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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/brute/message_transport/anthropic.rb +143 -0
  3. data/lib/brute/message_transport/llm.rb +94 -0
  4. data/lib/brute/message_transport/openai.rb +113 -0
  5. data/lib/brute/message_transport/ruby_llm.rb +78 -0
  6. data/lib/brute/message_transport.rb +133 -0
  7. data/lib/brute/messages.rb +79 -6
  8. data/lib/brute/middleware/002_session_log.rb +1 -1
  9. data/lib/brute/middleware/004_summarize.rb +7 -7
  10. data/lib/brute/middleware/006_loop.rb +4 -4
  11. data/lib/brute/middleware/008_checkpoint.rb +268 -0
  12. data/lib/brute/middleware/010_max_iterations.rb +1 -1
  13. data/lib/brute/middleware/020_system_prompt.rb +1 -1
  14. data/lib/brute/middleware/040_compaction_check.rb +2 -2
  15. data/lib/brute/middleware/070_tool_pipeline.rb +29 -26
  16. data/lib/brute/tool.rb +124 -0
  17. data/lib/brute/tools/adapter.rb +22 -60
  18. data/lib/brute/tools/fs_patch.rb +1 -1
  19. data/lib/brute/tools/fs_read.rb +1 -1
  20. data/lib/brute/tools/fs_remove.rb +1 -1
  21. data/lib/brute/tools/fs_search.rb +1 -1
  22. data/lib/brute/tools/fs_undo.rb +1 -1
  23. data/lib/brute/tools/fs_write.rb +1 -1
  24. data/lib/brute/tools/net_fetch.rb +1 -1
  25. data/lib/brute/tools/question.rb +1 -1
  26. data/lib/brute/tools/shell.rb +1 -1
  27. data/lib/brute/tools/skill_load.rb +1 -1
  28. data/lib/brute/tools/sub_agent.rb +5 -22
  29. data/lib/brute/tools/todo_read.rb +1 -1
  30. data/lib/brute/tools/todo_write.rb +1 -1
  31. data/lib/brute/turn/agent_pipeline.rb +2 -1
  32. data/lib/brute/turn/pipeline.rb +1 -1
  33. data/lib/brute/turn/tool_pipeline.rb +2 -12
  34. data/lib/brute/version.rb +1 -1
  35. data/lib/brute.rb +17 -18
  36. data/lib/brute_cli/providers/shell_response.rb +6 -10
  37. metadata +23 -17
  38. 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