brute 2.0.1 → 2.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88f91b95b420900fabdfb38701a146b0599b93da03f682f595fe3ef0c56ef73d
4
- data.tar.gz: 39211325840bbe237d5bbc58c626c5c0bcc916a3ebb4a02bfdd9313c277f0cc6
3
+ metadata.gz: e70352bd81dcba123054a2e5fb1c5bf12642aec892cf41fa48297ecbb61fea65
4
+ data.tar.gz: 242e0ef1477ec8201c13ce5ec535bd45abbad9dca4252c88366477487a3c0913
5
5
  SHA512:
6
- metadata.gz: fa8266cb20ac5a6c6bca2e062d696a98d6bae16d1a1dc5af71d4ed19dc7ad7c6253f639f75787beace5fc8e1182dc398d06ab06a267b200811766e658bce63de
7
- data.tar.gz: f57d43d509cd79006cc3678e742409a0dfee0d5037d8de419fce75532d06480e5c4684f8314292ab8d46bc966a25f34b15d0130d486c0f9de09f68c9b48d8e6d
6
+ metadata.gz: 1999c090d40c1cb55d2e64152a8c633d364a461c84ac337f2b80c7d1151fd732c18075ddcc0709001949dbafa098d2707485fdf9e812e2f9eb485494607c8ce9
7
+ data.tar.gz: 6097b281be949bc6cf52934952ddac860da082045811a8a167883071b6d90946ddd96832594589a9ae399277d638e754a404ba8fb1813e013e55b7856522b5d9
data/lib/brute/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Brute
4
- VERSION = "2.0.1"
4
+ VERSION = "2.0.2"
5
5
  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: 2.0.1
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brute Contributors
@@ -187,7 +187,6 @@ files:
187
187
  - lib/brute/system_prompt.rb
188
188
  - lib/brute/tool.rb
189
189
  - lib/brute/tools.rb
190
- - lib/brute/tools/delegate.rb
191
190
  - lib/brute/tools/fs_patch.rb
192
191
  - lib/brute/tools/fs_read.rb
193
192
  - lib/brute/tools/fs_remove.rb
@@ -1,109 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/setup"
4
- require "brute"
5
-
6
- module Brute
7
- module Tools
8
- class Delegate < RubyLLM::Tool
9
- description "Delegate a research or analysis task to a specialist sub-agent. " \
10
- "The sub-agent can read files and search but cannot write or execute commands. " \
11
- "Use for code analysis, understanding patterns, or gathering information."
12
-
13
- param :task, type: 'string', desc: "A clear, detailed description of the research task", required: true
14
-
15
- def name; "delegate"; end
16
-
17
- MAX_ROUNDS = 10
18
-
19
- def execute(task:)
20
- provider = Brute.provider
21
- llm = provider.ruby_llm_provider
22
- model_id = provider.default_model
23
- model = Brute::Middleware::ModelRef.new(model_id, 16_384)
24
-
25
- sub_tools = { read: FSRead.new, fs_search: FSSearch.new }
26
-
27
- messages = [
28
- RubyLLM::Message.new(
29
- role: :system,
30
- content: "You are a research agent. Analyze code, explain patterns, and answer questions. " \
31
- "You have read-only access to the filesystem. Be thorough and precise."
32
- ),
33
- RubyLLM::Message.new(role: :user, content: task),
34
- ]
35
-
36
- response = nil
37
- MAX_ROUNDS.times do
38
- response = llm.complete(messages, tools: sub_tools, temperature: nil, model: model)
39
- messages << response
40
-
41
- break unless response.tool_call?
42
-
43
- response.tool_calls.each_value do |tc|
44
- tool = sub_tools[tc.name.to_sym]
45
- result = if tool
46
- tool.call(tc.arguments)
47
- else
48
- { error: "Unknown tool: #{tc.name}" }
49
- end
50
- content = result.is_a?(String) ? result : result.to_s
51
- messages << RubyLLM::Message.new(role: :tool, content: content, tool_call_id: tc.id)
52
- end
53
- end
54
-
55
- { result: extract_content(response, messages) }
56
- end
57
-
58
- private
59
-
60
- # Safely extract text content from the sub-agent response.
61
- def extract_content(response, messages)
62
- text = response&.content
63
- return text if text.is_a?(::String) && !text.empty?
64
-
65
- # Fall back to last assistant text in the conversation history
66
- last_assistant = messages
67
- .select { |m| m.role == :assistant }
68
- .reverse
69
- .find { |m| m.content.is_a?(::String) && !m.content.empty? }
70
- last_assistant&.content || "(sub-agent completed but produced no text response)"
71
- end
72
- end
73
- end
74
- end
75
-
76
- test do
77
- require_relative "../../../spec/support/mock_provider"
78
- require_relative "../../../spec/support/mock_response"
79
-
80
- delegate = Brute::Tools::Delegate.new
81
-
82
- it "returns content when response has text" do
83
- res = RubyLLM::Message.new(role: :assistant, content: "analysis complete")
84
- delegate.send(:extract_content, res, []).should == "analysis complete"
85
- end
86
-
87
- it "falls back to last assistant text on nil content" do
88
- res = RubyLLM::Message.new(role: :assistant, content: "")
89
- msgs = [
90
- RubyLLM::Message.new(role: :user, content: "input"),
91
- RubyLLM::Message.new(role: :assistant, content: "found the answer"),
92
- ]
93
- delegate.send(:extract_content, res, msgs).should == "found the answer"
94
- end
95
-
96
- it "returns fallback when no assistant messages exist" do
97
- res = RubyLLM::Message.new(role: :assistant, content: "")
98
- delegate.send(:extract_content, res, []).should == "(sub-agent completed but produced no text response)"
99
- end
100
-
101
- it "skips assistant messages with empty content" do
102
- res = RubyLLM::Message.new(role: :assistant, content: "")
103
- msgs = [
104
- RubyLLM::Message.new(role: :assistant, content: "real answer"),
105
- RubyLLM::Message.new(role: :assistant, content: ""),
106
- ]
107
- delegate.send(:extract_content, res, msgs).should == "real answer"
108
- end
109
- end