brute 3.0.0 → 3.1.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/lib/brute/message_transport/anthropic.rb +143 -0
- data/lib/brute/message_transport/llm.rb +94 -0
- data/lib/brute/message_transport/openai.rb +113 -0
- data/lib/brute/message_transport/ruby_llm.rb +78 -0
- data/lib/brute/message_transport.rb +133 -0
- data/lib/brute/messages.rb +79 -6
- data/lib/brute/middleware/002_session_log.rb +1 -1
- data/lib/brute/middleware/004_summarize.rb +7 -7
- data/lib/brute/middleware/006_loop.rb +4 -4
- data/lib/brute/middleware/008_checkpoint.rb +268 -0
- data/lib/brute/middleware/010_max_iterations.rb +1 -1
- data/lib/brute/middleware/020_system_prompt.rb +1 -1
- data/lib/brute/middleware/040_compaction_check.rb +2 -2
- data/lib/brute/middleware/070_tool_pipeline.rb +29 -26
- data/lib/brute/tool.rb +124 -0
- data/lib/brute/tools/adapter.rb +22 -60
- data/lib/brute/tools/fs_patch.rb +1 -1
- data/lib/brute/tools/fs_read.rb +1 -1
- data/lib/brute/tools/fs_remove.rb +1 -1
- data/lib/brute/tools/fs_search.rb +1 -1
- data/lib/brute/tools/fs_undo.rb +1 -1
- data/lib/brute/tools/fs_write.rb +1 -1
- data/lib/brute/tools/net_fetch.rb +1 -1
- data/lib/brute/tools/question.rb +1 -1
- data/lib/brute/tools/shell.rb +1 -1
- data/lib/brute/tools/skill_load.rb +1 -1
- data/lib/brute/tools/sub_agent.rb +5 -22
- data/lib/brute/tools/todo_read.rb +1 -1
- data/lib/brute/tools/todo_write.rb +1 -1
- data/lib/brute/turn/agent_pipeline.rb +2 -1
- data/lib/brute/turn/pipeline.rb +1 -1
- data/lib/brute/turn/tool_pipeline.rb +2 -12
- data/lib/brute/version.rb +1 -1
- data/lib/brute.rb +17 -18
- data/lib/brute_cli/providers/shell_response.rb +6 -10
- metadata +23 -17
- data/lib/ruby_llm/message_transport.rb +0 -117
data/lib/brute/tools/adapter.rb
CHANGED
|
@@ -12,10 +12,9 @@ module Brute
|
|
|
12
12
|
# This solves three problems:
|
|
13
13
|
#
|
|
14
14
|
# 1. Using any tools library — anything that quacks like a tool
|
|
15
|
-
# (
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# Tools::SubAgent work without inheriting from a library class.
|
|
15
|
+
# (#name plus #call or #execute) is wrapped into the same interface.
|
|
16
|
+
# 2. Avoiding tool libraries entirely — Brute::Tool, Brute::Turn::ToolPipeline
|
|
17
|
+
# and Tools::SubAgent work without inheriting from a library class.
|
|
19
18
|
# 3. Quickly adding tools — a plain Hash with a proc is enough:
|
|
20
19
|
#
|
|
21
20
|
# Brute::Tools::Adapter.wrap(
|
|
@@ -32,8 +31,8 @@ module Brute
|
|
|
32
31
|
# adapter.params # { key => { type:, desc:, required: } }
|
|
33
32
|
# adapter.call(args) # execute with a (string- or symbol-keyed) Hash
|
|
34
33
|
#
|
|
35
|
-
#
|
|
36
|
-
# library expects
|
|
34
|
+
# The inline `run` proc converts adapters (via #to_h) into whatever its
|
|
35
|
+
# LLM library expects; ToolPipeline executes them via #call.
|
|
37
36
|
#
|
|
38
37
|
class Adapter
|
|
39
38
|
attr_reader :name, :description, :params
|
|
@@ -46,7 +45,7 @@ module Brute
|
|
|
46
45
|
|
|
47
46
|
case tool
|
|
48
47
|
when Hash then from_hash(tool)
|
|
49
|
-
when ::
|
|
48
|
+
when ::Brute::Tool then from_brute_tool(tool)
|
|
50
49
|
when Brute::Tools::SubAgent then new(
|
|
51
50
|
name: tool.name,
|
|
52
51
|
description: tool.description,
|
|
@@ -90,29 +89,21 @@ module Brute
|
|
|
90
89
|
)
|
|
91
90
|
end
|
|
92
91
|
|
|
93
|
-
# A
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
def self.from_ruby_llm(tool)
|
|
97
|
-
params = tool.parameters.each_with_object({}) do |(key, param), hash|
|
|
98
|
-
hash[key.to_sym] = { type: param.type, desc: param.description, required: param.required }.compact
|
|
99
|
-
end
|
|
100
|
-
|
|
92
|
+
# A Brute::Tool instance. Tools declared with the params({...}) schema
|
|
93
|
+
# DSL keep their full JSON schema.
|
|
94
|
+
def self.from_brute_tool(tool)
|
|
101
95
|
new(
|
|
102
96
|
name: tool.name.to_s,
|
|
103
97
|
description: tool.description,
|
|
104
|
-
params: params,
|
|
105
|
-
schema:
|
|
98
|
+
params: tool.params,
|
|
99
|
+
schema: tool.params_schema,
|
|
106
100
|
handler: ->(**args) { tool.call(args) },
|
|
107
101
|
original: tool,
|
|
108
102
|
)
|
|
109
103
|
end
|
|
110
104
|
|
|
111
|
-
# Anything tool-shaped: needs #name and #call or #execute.
|
|
112
|
-
# #to_ruby_llm for backward compatibility with existing adapters.
|
|
105
|
+
# Anything tool-shaped: needs #name and #call or #execute.
|
|
113
106
|
def self.from_duck_type(tool)
|
|
114
|
-
return from_ruby_llm(tool.to_ruby_llm) if tool.respond_to?(:to_ruby_llm)
|
|
115
|
-
|
|
116
107
|
unless tool.respond_to?(:name) && (tool.respond_to?(:call) || tool.respond_to?(:execute))
|
|
117
108
|
raise ArgumentError, "don't know how to adapt #{tool.inspect} into a tool"
|
|
118
109
|
end
|
|
@@ -136,7 +127,7 @@ module Brute
|
|
|
136
127
|
@original = original
|
|
137
128
|
end
|
|
138
129
|
|
|
139
|
-
# The tool object this adapter wraps (
|
|
130
|
+
# The tool object this adapter wraps (Brute::Tool, Brute::Turn::ToolPipeline,
|
|
140
131
|
# SubAgent, Hash definition, ...).
|
|
141
132
|
attr_reader :original
|
|
142
133
|
|
|
@@ -147,23 +138,8 @@ module Brute
|
|
|
147
138
|
@handler.call(**args)
|
|
148
139
|
end
|
|
149
140
|
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
# it already is one.
|
|
153
|
-
def to_ruby_llm
|
|
154
|
-
return @original if @original.is_a?(::RubyLLM::Tool)
|
|
155
|
-
|
|
156
|
-
adapter = self
|
|
157
|
-
Class.new(::RubyLLM::Tool) do
|
|
158
|
-
description adapter.description
|
|
159
|
-
adapter.params.each { |key, opts| param key, **opts.slice(:type, :desc, :required) }
|
|
160
|
-
define_method(:name) { adapter.name }
|
|
161
|
-
define_method(:execute) { |**args| adapter.call(args) }
|
|
162
|
-
end.new
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Library-neutral tool definition (JSON-Schema-ish), for completion
|
|
166
|
-
# middlewares that talk to an HTTP API directly.
|
|
141
|
+
# Library-neutral tool definition (JSON-Schema-ish). The inline `run`
|
|
142
|
+
# proc reshapes this into whatever its LLM library expects.
|
|
167
143
|
def to_h
|
|
168
144
|
return { name: @name, description: @description, parameters: @schema.deep_symbolize_keys } if @schema
|
|
169
145
|
|
|
@@ -194,16 +170,16 @@ end
|
|
|
194
170
|
__END__
|
|
195
171
|
|
|
196
172
|
describe "brute/tools/adapter" do
|
|
197
|
-
it "wraps a
|
|
198
|
-
klass = Class.new(::
|
|
173
|
+
it "wraps a Brute::Tool class" do
|
|
174
|
+
klass = Class.new(::Brute::Tool) do
|
|
199
175
|
description "test tool"
|
|
200
176
|
param :input, type: "string", desc: "the input"
|
|
201
|
-
def name; "
|
|
177
|
+
def name; "brute_tool"; end
|
|
202
178
|
def execute(input:); "got #{input}"; end
|
|
203
179
|
end
|
|
204
180
|
|
|
205
181
|
adapter = Brute::Tools::Adapter.wrap(klass)
|
|
206
|
-
adapter.name.should == "
|
|
182
|
+
adapter.name.should == "brute_tool"
|
|
207
183
|
adapter.description.should == "test tool"
|
|
208
184
|
adapter.params[:input][:type].should == "string"
|
|
209
185
|
adapter.call("input" => "x").should == "got x"
|
|
@@ -255,29 +231,15 @@ describe "brute/tools/adapter" do
|
|
|
255
231
|
tools[:a].should.be.kind_of?(Brute::Tools::Adapter)
|
|
256
232
|
end
|
|
257
233
|
|
|
258
|
-
it "
|
|
259
|
-
|
|
260
|
-
name: "echo",
|
|
261
|
-
description: "Echo",
|
|
262
|
-
params: { msg: { type: "string", desc: "message", required: true } },
|
|
263
|
-
execute: ->(msg:) { msg },
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
rl = adapter.to_ruby_llm
|
|
267
|
-
rl.should.be.kind_of?(::RubyLLM::Tool)
|
|
268
|
-
rl.name.should == "echo"
|
|
269
|
-
rl.call("msg" => "hello").should == "hello"
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
it "returns the original when it already is a RubyLLM::Tool" do
|
|
273
|
-
klass = Class.new(::RubyLLM::Tool) do
|
|
234
|
+
it "exposes the wrapped original" do
|
|
235
|
+
klass = Class.new(::Brute::Tool) do
|
|
274
236
|
description "test tool"
|
|
275
237
|
def name; "original"; end
|
|
276
238
|
def execute; "ok"; end
|
|
277
239
|
end
|
|
278
240
|
instance = klass.new
|
|
279
241
|
|
|
280
|
-
Brute::Tools::Adapter.wrap(instance).
|
|
242
|
+
Brute::Tools::Adapter.wrap(instance).original.should == instance
|
|
281
243
|
end
|
|
282
244
|
|
|
283
245
|
it "produces a neutral JSON-schema-ish definition" do
|
data/lib/brute/tools/fs_patch.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "brute/tools"
|
|
|
6
6
|
|
|
7
7
|
module Brute
|
|
8
8
|
module Tools
|
|
9
|
-
class FSPatch <
|
|
9
|
+
class FSPatch < Brute::Tool
|
|
10
10
|
description 'Replace a specific string in a file. The old_string must match exactly ' \
|
|
11
11
|
'(including whitespace and indentation). Always read a file before patching it.'
|
|
12
12
|
|
data/lib/brute/tools/fs_read.rb
CHANGED
|
@@ -28,7 +28,7 @@ module Brute
|
|
|
28
28
|
# 8. Return a plain string instead of a Hash — avoids the .to_s repr
|
|
29
29
|
# bloat when ToolPipeline coerces the result for the LLM message.
|
|
30
30
|
#
|
|
31
|
-
class FSRead <
|
|
31
|
+
class FSRead < Brute::Tool
|
|
32
32
|
description "Read the contents of a file. Returns file content with line numbers. " \
|
|
33
33
|
"Use start_line/end_line for partial reads of large files."
|
|
34
34
|
|
|
@@ -7,7 +7,7 @@ require "fileutils"
|
|
|
7
7
|
|
|
8
8
|
module Brute
|
|
9
9
|
module Tools
|
|
10
|
-
class FSRemove <
|
|
10
|
+
class FSRemove < Brute::Tool
|
|
11
11
|
description "Remove a file or empty directory."
|
|
12
12
|
|
|
13
13
|
param :path, type: 'string', desc: "Path to the file or directory to remove", required: true
|
|
@@ -21,7 +21,7 @@ module Brute
|
|
|
21
21
|
# 5. Return a plain string instead of a Hash.
|
|
22
22
|
# 6. Align output cap with universal truncation (2000 lines / 50 KB).
|
|
23
23
|
#
|
|
24
|
-
class FSSearch <
|
|
24
|
+
class FSSearch < Brute::Tool
|
|
25
25
|
description "Search file contents using ripgrep (regex), or find files by glob pattern. " \
|
|
26
26
|
"Returns matching lines with file paths and line numbers."
|
|
27
27
|
|
data/lib/brute/tools/fs_undo.rb
CHANGED
data/lib/brute/tools/fs_write.rb
CHANGED
|
@@ -7,7 +7,7 @@ require 'fileutils'
|
|
|
7
7
|
|
|
8
8
|
module Brute
|
|
9
9
|
module Tools
|
|
10
|
-
class FSWrite <
|
|
10
|
+
class FSWrite < Brute::Tool
|
|
11
11
|
description "Write content to a file. Creates parent directories if they don't exist. " \
|
|
12
12
|
'Use this for creating new files or completely replacing file contents.'
|
|
13
13
|
|
|
@@ -9,7 +9,7 @@ require "uri"
|
|
|
9
9
|
|
|
10
10
|
module Brute
|
|
11
11
|
module Tools
|
|
12
|
-
class NetFetch <
|
|
12
|
+
class NetFetch < Brute::Tool
|
|
13
13
|
description "Fetch content from a URL. Returns the response body as text."
|
|
14
14
|
|
|
15
15
|
param :url, type: 'string', desc: "The URL to fetch", required: true
|
data/lib/brute/tools/question.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "brute/tools"
|
|
|
6
6
|
|
|
7
7
|
module Brute
|
|
8
8
|
module Tools
|
|
9
|
-
class Question <
|
|
9
|
+
class Question < Brute::Tool
|
|
10
10
|
description "Ask the user questions during execution. Use this to gather preferences, " \
|
|
11
11
|
"clarify ambiguous instructions, get decisions on implementation choices, or " \
|
|
12
12
|
"offer choices about direction. Users can always select \"Other\" to provide " \
|
data/lib/brute/tools/shell.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Brute
|
|
|
22
22
|
# the LLM (defaults to 5 minutes).
|
|
23
23
|
# 5. Return a plain string instead of a Hash.
|
|
24
24
|
#
|
|
25
|
-
class Shell <
|
|
25
|
+
class Shell < Brute::Tool
|
|
26
26
|
description "Execute a shell command and return stdout, stderr, and exit code. " \
|
|
27
27
|
"Use for git operations, running tests, installing packages, etc."
|
|
28
28
|
|
|
@@ -28,7 +28,7 @@ module Brute
|
|
|
28
28
|
# tool with the matching cwd: Brute::Tools::SkillLoad.new(cwd: __dir__).
|
|
29
29
|
#
|
|
30
30
|
# Reference: opencode's tool/skill.ts.
|
|
31
|
-
class SkillLoad <
|
|
31
|
+
class SkillLoad < Brute::Tool
|
|
32
32
|
description "Load a specialized skill when the task at hand matches one of the " \
|
|
33
33
|
"available skills listed in the system context. This injects the skill's " \
|
|
34
34
|
"full instructions into the conversation, plus the skill's base directory " \
|
|
@@ -23,14 +23,10 @@ module Brute
|
|
|
23
23
|
# use Brute::Middleware::MaxIterations, max_iterations: 10
|
|
24
24
|
# use Brute::Middleware::ToolPipeline, tools: [Brute::Tools::FSRead, Brute::Tools::FSSearch]
|
|
25
25
|
# run ->(env) do
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# tools: Brute.rubyllm_tools(env[:tools]),
|
|
31
|
-
# temperature: 0.7,
|
|
32
|
-
# model: model)
|
|
33
|
-
# RubyLLM::MessageTransport.new(response).wrap_each { |m| env[:messages] << m }
|
|
26
|
+
# # The LLM call, written with your library of choice. Convert
|
|
27
|
+
# # env[:messages] to its format, call it, and append the response
|
|
28
|
+
# # back as Brute::Message values (the MessageTransport pattern —
|
|
29
|
+
# # see examples/ruby_llm.rb, examples/openai.rb, ...).
|
|
34
30
|
# end
|
|
35
31
|
# end
|
|
36
32
|
#
|
|
@@ -63,20 +59,7 @@ module Brute
|
|
|
63
59
|
extract_result(session)
|
|
64
60
|
end
|
|
65
61
|
|
|
66
|
-
#
|
|
67
|
-
# sees this as a regular tool. ToolPipeline middleware should call
|
|
68
|
-
# `to_ruby_llm` when building the tools hash if a tool responds to it.
|
|
69
|
-
def to_ruby_llm
|
|
70
|
-
sub = self
|
|
71
|
-
Class.new(::RubyLLM::Tool) do
|
|
72
|
-
description sub.description
|
|
73
|
-
sub.params.each { |k, opts| param k, **opts }
|
|
74
|
-
define_method(:name) { sub.sub_agent_name }
|
|
75
|
-
define_method(:execute) { |**args| sub.execute(args) }
|
|
76
|
-
end.new
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Lets ToolPipeline treat SubAgents the same as RubyLLM::Tool instances
|
|
62
|
+
# Lets ToolPipeline treat SubAgents the same as any other tool
|
|
80
63
|
# without checking respond_to? everywhere.
|
|
81
64
|
def name
|
|
82
65
|
@sub_agent_name
|
|
@@ -6,7 +6,7 @@ require "brute/tools"
|
|
|
6
6
|
|
|
7
7
|
module Brute
|
|
8
8
|
module Tools
|
|
9
|
-
class TodoRead <
|
|
9
|
+
class TodoRead < Brute::Tool
|
|
10
10
|
description "Read the current todo list to check task status and progress."
|
|
11
11
|
param :_placeholder, type: 'string', desc: "Unused, pass any value", required: false
|
|
12
12
|
|
|
@@ -57,7 +57,8 @@ module Brute
|
|
|
57
57
|
case input
|
|
58
58
|
when nil then Brute.log
|
|
59
59
|
when ::String then Brute.log.tap { |log| log.user(input) }
|
|
60
|
-
when ::
|
|
60
|
+
when ::Brute::Message then Brute.log(input)
|
|
61
|
+
when ::Hash then Brute.log(Brute::Message.new(**input.transform_keys(&:to_sym)))
|
|
61
62
|
when ::Array then input.extend(Brute::Messages)
|
|
62
63
|
else Brute.log(input)
|
|
63
64
|
end
|
data/lib/brute/turn/pipeline.rb
CHANGED
|
@@ -13,8 +13,8 @@ module Brute
|
|
|
13
13
|
# the work; middleware wraps it with concerns like file mutation queueing,
|
|
14
14
|
# validation, logging.
|
|
15
15
|
#
|
|
16
|
-
# Coexists with Brute::Tools::* (which inherit from
|
|
17
|
-
# ToolPipeline when you want middleware; use
|
|
16
|
+
# Coexists with Brute::Tools::* (which inherit from Brute::Tool). Use a
|
|
17
|
+
# ToolPipeline when you want middleware; use Brute::Tool subclasses for
|
|
18
18
|
# simple cases.
|
|
19
19
|
#
|
|
20
20
|
# read = Brute::Turn::ToolPipeline.new(
|
|
@@ -53,16 +53,6 @@ module Brute
|
|
|
53
53
|
env[:result]
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
# Adapter so the LLM can call this tool through ruby_llm.
|
|
57
|
-
def to_ruby_llm
|
|
58
|
-
tool = self
|
|
59
|
-
Class.new(RubyLLM::Tool) do
|
|
60
|
-
description tool.description
|
|
61
|
-
tool.params.each { |k, opts| param k, **opts }
|
|
62
|
-
define_method(:name) { tool.name }
|
|
63
|
-
define_method(:execute) { |**args| tool.call(**args) }
|
|
64
|
-
end.new
|
|
65
|
-
end
|
|
66
56
|
end
|
|
67
57
|
end
|
|
68
58
|
end
|
data/lib/brute/version.rb
CHANGED
data/lib/brute.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "bundler/setup"
|
|
4
4
|
|
|
5
|
-
require "ruby_llm"
|
|
6
5
|
require "rack"
|
|
7
6
|
require 'timeout'
|
|
8
7
|
require 'logger'
|
|
@@ -22,16 +21,14 @@ module Brute
|
|
|
22
21
|
`Y8bod8P' d888b `V88V"V8P' "888" `Y8bod8P'
|
|
23
22
|
LOGO
|
|
24
23
|
|
|
25
|
-
# NOTE: Brute owns no LLM configuration
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# See examples/agents/01_basic_agent.rb.
|
|
24
|
+
# NOTE: Brute owns no LLM configuration and no LLM library. All
|
|
25
|
+
# provider/model/credential config lives in the pipeline's terminal `run`
|
|
26
|
+
# proc, which the user writes with whatever LLM library they prefer
|
|
27
|
+
# (ruby_llm, llm.rb, openai, anthropic, raw HTTP, ...). The proc converts
|
|
28
|
+
# env[:messages] (Brute::Message values — see Brute.log) to the library's
|
|
29
|
+
# format, makes the call, and appends the response back as Brute::Message
|
|
30
|
+
# values — the MessageTransport pattern. See examples/ruby_llm.rb,
|
|
31
|
+
# examples/llm.rb, examples/openai.rb and examples/anthropic.rb.
|
|
35
32
|
|
|
36
33
|
def self.provider
|
|
37
34
|
@provider ||= :anthropic
|
|
@@ -60,19 +57,21 @@ module Brute
|
|
|
60
57
|
Brute::Turn::AgentPipeline.new(&block)
|
|
61
58
|
end
|
|
62
59
|
|
|
63
|
-
# Adapt any Brute tools (hashes, Brute::Turn::ToolPipeline,
|
|
64
|
-
# into a { name_sym =>
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
# Adapt any Brute tools (hashes, Brute::Tool, Brute::Turn::ToolPipeline,
|
|
61
|
+
# SubAgent …) into a { name_sym => Brute::Tools::Adapter } hash. Each
|
|
62
|
+
# adapter exposes #to_h — a neutral JSON-Schema-ish definition the inline
|
|
63
|
+
# `run` proc converts to whatever its LLM library expects.
|
|
64
|
+
def self.tools(tools)
|
|
65
|
+
Brute::Tools::Adapter.wrap_all(tools || [])
|
|
68
66
|
end
|
|
69
|
-
|
|
67
|
+
|
|
68
|
+
|
|
70
69
|
def self.provider=(p)
|
|
71
70
|
@provider = p.to_sym
|
|
72
71
|
end
|
|
73
72
|
end
|
|
74
73
|
|
|
75
|
-
Dir.glob("#{__dir__}/
|
|
74
|
+
Dir.glob("#{__dir__}/brute/**/*.rb").sort.each do |path|
|
|
76
75
|
require path
|
|
77
76
|
end
|
|
78
77
|
|
|
@@ -28,15 +28,15 @@ module BruteCLI
|
|
|
28
28
|
return [empty_assistant] if @command.nil?
|
|
29
29
|
|
|
30
30
|
call_id = "shell_#{SecureRandom.hex(8)}"
|
|
31
|
-
tool_calls =
|
|
32
|
-
|
|
31
|
+
tool_calls = [
|
|
32
|
+
Brute::ToolCall.new(
|
|
33
33
|
id: call_id,
|
|
34
34
|
name: "shell",
|
|
35
35
|
arguments: { "command" => @command },
|
|
36
36
|
)
|
|
37
|
-
|
|
37
|
+
]
|
|
38
38
|
|
|
39
|
-
[
|
|
39
|
+
[Brute::Message.new(
|
|
40
40
|
role: :assistant,
|
|
41
41
|
content: "",
|
|
42
42
|
tool_calls: tool_calls,
|
|
@@ -78,17 +78,13 @@ module BruteCLI
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def usage
|
|
81
|
-
|
|
82
|
-
input: 0,
|
|
83
|
-
output: 0,
|
|
84
|
-
reasoning: 0,
|
|
85
|
-
)
|
|
81
|
+
{ input: 0, output: 0, reasoning: 0 }
|
|
86
82
|
end
|
|
87
83
|
|
|
88
84
|
private
|
|
89
85
|
|
|
90
86
|
def empty_assistant
|
|
91
|
-
|
|
87
|
+
Brute::Message.new(role: :assistant, content: "")
|
|
92
88
|
end
|
|
93
89
|
end
|
|
94
90
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brute
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brute Contributors
|
|
@@ -37,20 +37,6 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '1.5'
|
|
40
|
-
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: ruby_llm
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - ">="
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0'
|
|
47
|
-
type: :runtime
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - ">="
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0'
|
|
54
40
|
- !ruby/object:Gem::Dependency
|
|
55
41
|
name: activesupport
|
|
56
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -163,6 +149,20 @@ dependencies:
|
|
|
163
149
|
- - "~>"
|
|
164
150
|
- !ruby/object:Gem::Version
|
|
165
151
|
version: '1.0'
|
|
152
|
+
- !ruby/object:Gem::Dependency
|
|
153
|
+
name: lefthook
|
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - "~>"
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '2.1'
|
|
159
|
+
type: :development
|
|
160
|
+
prerelease: false
|
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - "~>"
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '2.1'
|
|
166
166
|
description: Production-grade coding agent with tool execution, middleware pipeline,
|
|
167
167
|
context compaction, session persistence, and multi-provider LLM support.
|
|
168
168
|
executables: []
|
|
@@ -173,12 +173,18 @@ files:
|
|
|
173
173
|
- lib/brute/events/handler.rb
|
|
174
174
|
- lib/brute/events/prefixed_terminal_output.rb
|
|
175
175
|
- lib/brute/events/terminal_output_handler.rb
|
|
176
|
+
- lib/brute/message_transport.rb
|
|
177
|
+
- lib/brute/message_transport/anthropic.rb
|
|
178
|
+
- lib/brute/message_transport/llm.rb
|
|
179
|
+
- lib/brute/message_transport/openai.rb
|
|
180
|
+
- lib/brute/message_transport/ruby_llm.rb
|
|
176
181
|
- lib/brute/messages.rb
|
|
177
182
|
- lib/brute/middleware/001_otel_span.rb
|
|
178
183
|
- lib/brute/middleware/002_session_log.rb
|
|
179
184
|
- lib/brute/middleware/004_summarize.rb
|
|
180
185
|
- lib/brute/middleware/005_tracing.rb
|
|
181
186
|
- lib/brute/middleware/006_loop.rb
|
|
187
|
+
- lib/brute/middleware/008_checkpoint.rb
|
|
182
188
|
- lib/brute/middleware/010_max_iterations.rb
|
|
183
189
|
- lib/brute/middleware/015_otel_token_usage.rb
|
|
184
190
|
- lib/brute/middleware/020_system_prompt.rb
|
|
@@ -234,6 +240,7 @@ files:
|
|
|
234
240
|
- lib/brute/rack/adapter.rb
|
|
235
241
|
- lib/brute/skill.rb
|
|
236
242
|
- lib/brute/system_prompt.rb
|
|
243
|
+
- lib/brute/tool.rb
|
|
237
244
|
- lib/brute/tools.rb
|
|
238
245
|
- lib/brute/tools/adapter.rb
|
|
239
246
|
- lib/brute/tools/fs/file_mutation_queue.rb
|
|
@@ -260,7 +267,6 @@ files:
|
|
|
260
267
|
- lib/brute/version.rb
|
|
261
268
|
- lib/brute_cli/providers/shell.rb
|
|
262
269
|
- lib/brute_cli/providers/shell_response.rb
|
|
263
|
-
- lib/ruby_llm/message_transport.rb
|
|
264
270
|
homepage: https://github.com/general-intelligence-systems/brute
|
|
265
271
|
licenses:
|
|
266
272
|
- MIT
|
|
@@ -282,5 +288,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
282
288
|
requirements: []
|
|
283
289
|
rubygems_version: 3.7.2
|
|
284
290
|
specification_version: 4
|
|
285
|
-
summary: A coding agent
|
|
291
|
+
summary: A framework-agnostic coding agent
|
|
286
292
|
test_files: []
|