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
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "brute"
|
|
5
|
-
|
|
6
|
-
module RubyLLM
|
|
7
|
-
# Transports the result of an LLM call into Brute's message format.
|
|
8
|
-
#
|
|
9
|
-
# Calling an LLM is trivial — `RubyLLM.chat.ask "..."` — so Brute has no
|
|
10
|
-
# "completion middleware". The terminal `run` of an agent pipeline is an
|
|
11
|
-
# inline proc that makes the LLM call itself. The only wrinkle: RubyLLM hands
|
|
12
|
-
# back its own objects (a Message, an array, a whole Chat transcript), while
|
|
13
|
-
# the rest of the stack — the turn manager, event handlers, tool loop and
|
|
14
|
-
# SessionLog persistence — works off `env[:messages]` (a plain message log;
|
|
15
|
-
# see Brute.log).
|
|
16
|
-
#
|
|
17
|
-
# This makes NO LLM call and does NO appending. It just wraps what the proc
|
|
18
|
-
# got back and yields each message in Brute's format; the proc appends:
|
|
19
|
-
#
|
|
20
|
-
# response = provider.complete(env[:messages], ...) # one Message
|
|
21
|
-
# RubyLLM::MessageTransport.new(response).wrap_each do |message|
|
|
22
|
-
# env[:messages] << message
|
|
23
|
-
# end
|
|
24
|
-
#
|
|
25
|
-
# before = chat.messages.length # a Chat loop
|
|
26
|
-
# chat.complete
|
|
27
|
-
# RubyLLM::MessageTransport.new(chat.messages[before..]).wrap_each do |message|
|
|
28
|
-
# env[:messages] << message
|
|
29
|
-
# end
|
|
30
|
-
class MessageTransport
|
|
31
|
-
# Convenience: RubyLLM::MessageTransport.wrap_each(result) { |m| ... }
|
|
32
|
-
def self.wrap_each(result, &block)
|
|
33
|
-
new(result).wrap_each(&block)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def initialize(result)
|
|
37
|
-
@result = result
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Yield each result message in Brute's format. Without a block, returns an
|
|
41
|
-
# Enumerator. The caller decides what to do with each (typically append to
|
|
42
|
-
# env[:messages]).
|
|
43
|
-
def wrap_each
|
|
44
|
-
return enum_for(:wrap_each) unless block_given?
|
|
45
|
-
|
|
46
|
-
messages.each { |message| yield wrap(message) }
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# The result normalized to a flat list of messages. A single Message, an
|
|
50
|
-
# array, or anything transcript-shaped (a Chat responds to #messages).
|
|
51
|
-
def messages
|
|
52
|
-
case @result
|
|
53
|
-
when ::RubyLLM::Message then [@result]
|
|
54
|
-
when Array then @result.compact
|
|
55
|
-
else @result.respond_to?(:messages) ? @result.messages : Array(@result)
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
private
|
|
60
|
-
|
|
61
|
-
# The Brute-format view of a single message. RubyLLM::Message already IS
|
|
62
|
-
# Brute's message format, so this is the identity seam — the place to
|
|
63
|
-
# normalize if the two formats ever diverge.
|
|
64
|
-
def wrap(message)
|
|
65
|
-
message
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
__END__
|
|
71
|
-
|
|
72
|
-
describe "ruby_llm/message_transport" do
|
|
73
|
-
require "brute/messages"
|
|
74
|
-
|
|
75
|
-
it "wraps a single message" do
|
|
76
|
-
message = RubyLLM::Message.new(role: :assistant, content: "hi")
|
|
77
|
-
RubyLLM::MessageTransport.new(message).wrap_each.to_a.should == [message]
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
it "wraps an array of messages" do
|
|
81
|
-
a = RubyLLM::Message.new(role: :assistant, content: "one")
|
|
82
|
-
b = RubyLLM::Message.new(role: :tool, content: "two", tool_call_id: "tc1")
|
|
83
|
-
RubyLLM::MessageTransport.new([a, b]).wrap_each.to_a.should == [a, b]
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
it "wraps a transcript-shaped object (a Chat)" do
|
|
87
|
-
fake_chat = Class.new do
|
|
88
|
-
attr_reader :messages
|
|
89
|
-
def initialize(messages); @messages = messages; end
|
|
90
|
-
end
|
|
91
|
-
msgs = [RubyLLM::Message.new(role: :user, content: "hi"),
|
|
92
|
-
RubyLLM::Message.new(role: :assistant, content: "hello")]
|
|
93
|
-
|
|
94
|
-
RubyLLM::MessageTransport.new(fake_chat.new(msgs)).wrap_each.to_a.map(&:role).should == [:user, :assistant]
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
it "yields each message to the block for the caller to append" do
|
|
98
|
-
session = Brute.log
|
|
99
|
-
session.user("hello")
|
|
100
|
-
response = RubyLLM::Message.new(role: :assistant, content: "hi there")
|
|
101
|
-
|
|
102
|
-
RubyLLM::MessageTransport.new(response).wrap_each { |message| session << message }
|
|
103
|
-
|
|
104
|
-
session.last.role.should == :assistant
|
|
105
|
-
session.last.content.should == "hi there"
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
it "exposes a class-level wrap_each convenience" do
|
|
109
|
-
session = Brute.log
|
|
110
|
-
a = RubyLLM::Message.new(role: :assistant, content: "a")
|
|
111
|
-
b = RubyLLM::Message.new(role: :assistant, content: "b")
|
|
112
|
-
|
|
113
|
-
RubyLLM::MessageTransport.wrap_each([a, b]) { |message| session << message }
|
|
114
|
-
|
|
115
|
-
session.map(&:content).should == ["a", "b"]
|
|
116
|
-
end
|
|
117
|
-
end
|