brute 2.0.6 → 3.0.1
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/events/handler.rb +3 -1
- data/lib/brute/events/prefixed_terminal_output.rb +3 -1
- data/lib/brute/events/terminal_output_handler.rb +3 -1
- 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 +120 -0
- data/lib/brute/middleware/001_otel_span.rb +4 -2
- data/lib/brute/middleware/002_session_log.rb +100 -0
- data/lib/brute/middleware/004_summarize.rb +21 -19
- data/lib/brute/middleware/006_loop.rb +158 -0
- data/lib/brute/middleware/010_max_iterations.rb +9 -7
- data/lib/brute/middleware/015_otel_token_usage.rb +3 -1
- data/lib/brute/middleware/020_system_prompt.rb +6 -4
- data/lib/brute/middleware/040_compaction_check.rb +5 -3
- data/lib/brute/middleware/060_questions.rb +3 -1
- data/lib/brute/middleware/{070_tool_call.rb → 070_tool_pipeline.rb} +60 -46
- data/lib/brute/middleware/073_otel_tool_call.rb +3 -1
- data/lib/brute/middleware/075_otel_tool_results.rb +3 -1
- data/lib/brute/middleware/event_handler.rb +3 -1
- data/lib/brute/middleware/user_queue.rb +3 -1
- data/lib/brute/prompts/autonomy.rb +3 -0
- data/lib/brute/prompts/base.rb +3 -0
- data/lib/brute/prompts/build_switch.rb +3 -1
- data/lib/brute/prompts/code_references.rb +3 -0
- data/lib/brute/prompts/code_style.rb +3 -0
- data/lib/brute/prompts/conventions.rb +3 -0
- data/lib/brute/prompts/doing_tasks.rb +3 -0
- data/lib/brute/prompts/editing_approach.rb +3 -0
- data/lib/brute/prompts/editing_constraints.rb +3 -0
- data/lib/brute/prompts/environment.rb +3 -1
- data/lib/brute/prompts/frontend_tasks.rb +3 -0
- data/lib/brute/prompts/git_safety.rb +3 -0
- data/lib/brute/prompts/identity.rb +3 -1
- data/lib/brute/prompts/instructions.rb +3 -1
- data/lib/brute/prompts/max_steps.rb +3 -1
- data/lib/brute/prompts/objectivity.rb +3 -0
- data/lib/brute/prompts/plan_reminder.rb +3 -1
- data/lib/brute/prompts/proactiveness.rb +3 -0
- data/lib/brute/prompts/security_and_safety.rb +3 -0
- data/lib/brute/prompts/skills.rb +6 -2
- data/lib/brute/prompts/task_management.rb +3 -0
- data/lib/brute/prompts/tone_and_style.rb +3 -0
- data/lib/brute/prompts/tool_usage.rb +3 -0
- data/lib/brute/prompts.rb +5 -0
- data/lib/brute/rack/adapter.rb +237 -0
- data/lib/brute/skill.rb +192 -15
- data/lib/brute/system_prompt.rb +3 -1
- data/lib/brute/tool.rb +92 -75
- data/lib/brute/tools/adapter.rb +258 -0
- data/lib/brute/tools/fs/file_mutation_queue.rb +107 -0
- data/lib/brute/tools/fs/snapshot_store.rb +41 -0
- data/lib/brute/tools/fs_patch.rb +6 -4
- data/lib/brute/tools/fs_read.rb +5 -3
- data/lib/brute/tools/fs_remove.rb +3 -3
- data/lib/brute/tools/fs_search.rb +4 -2
- data/lib/brute/tools/fs_undo.rb +3 -3
- data/lib/brute/tools/fs_write.rb +6 -4
- data/lib/brute/tools/net_fetch.rb +1 -1
- data/lib/brute/tools/question.rb +1 -1
- data/lib/brute/tools/shell.rb +4 -2
- data/lib/brute/tools/skill_load.rb +156 -0
- data/lib/brute/tools/sub_agent.rb +101 -0
- data/lib/brute/tools/todo_list/store.rb +36 -0
- data/lib/brute/tools/todo_read.rb +2 -2
- data/lib/brute/tools/todo_write.rb +2 -2
- data/lib/brute/tools.rb +2 -1
- data/lib/brute/truncation.rb +3 -1
- data/lib/brute/turn/agent_pipeline.rb +169 -0
- data/lib/brute/turn/pipeline.rb +95 -0
- data/lib/brute/turn/tool_pipeline.rb +96 -0
- data/lib/brute/utils/diff.rb +3 -1
- data/lib/brute/version.rb +1 -1
- data/lib/brute.rb +45 -71
- data/lib/{brute → brute_cli}/providers/shell.rb +4 -1
- data/lib/{brute → brute_cli}/providers/shell_response.rb +9 -15
- metadata +52 -43
- data/lib/brute/agent.rb +0 -82
- data/lib/brute/middleware/003_tool_result_loop.rb +0 -103
- data/lib/brute/middleware/100_llm_call.rb +0 -63
- data/lib/brute/pipeline.rb +0 -97
- data/lib/brute/queue/file_mutation_queue.rb +0 -102
- data/lib/brute/session.rb +0 -51
- data/lib/brute/store/snapshot_store.rb +0 -36
- data/lib/brute/store/todo_store.rb +0 -30
- data/lib/brute/sub_agent.rb +0 -106
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "brute"
|
|
5
|
+
|
|
6
|
+
module Brute
|
|
7
|
+
module Turn
|
|
8
|
+
class Pipeline < ::Rack::Builder
|
|
9
|
+
module Chainable
|
|
10
|
+
# Enables the following syntax:
|
|
11
|
+
#
|
|
12
|
+
# Brute.agent
|
|
13
|
+
# .use(UltraSecurity)
|
|
14
|
+
# .use(MaxProfit)
|
|
15
|
+
# .use(DontTellMom)
|
|
16
|
+
# .run -> (env) {
|
|
17
|
+
# your_llm_library.complete("How to make money?")
|
|
18
|
+
# }
|
|
19
|
+
#
|
|
20
|
+
def use(...) = tap { super }
|
|
21
|
+
def run(...) = tap { super }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
include Chainable
|
|
25
|
+
|
|
26
|
+
# Rack's `new_from_string` evaluates the script then returns `to_app` —
|
|
27
|
+
# the built callable. An agent, though, is the *builder*: you `.start` it,
|
|
28
|
+
# and it may be re-`use`d or served through the Rack adapter. So evaluate
|
|
29
|
+
# the script against a fresh builder and hand back the builder itself.
|
|
30
|
+
# This also backs `parse_file` (→ `load_file` → here), so
|
|
31
|
+
# `AgentPipeline.parse_file("agent.ru").start(prompt)` works as documented.
|
|
32
|
+
def self.new_from_string(builder_script, path = "(rackup)", **options)
|
|
33
|
+
builder = new(**options)
|
|
34
|
+
eval(builder_script, ::Rack::BUILDER_TOPLEVEL_BINDING.call(builder), path) # rubocop:disable Security/Eval
|
|
35
|
+
builder
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Brute's name for Rack's `to_app`: nest the middleware around the
|
|
39
|
+
# terminal app and return the runnable callable. Raises (via `to_app`)
|
|
40
|
+
# when `run` was never called.
|
|
41
|
+
alias_method :build, :to_app
|
|
42
|
+
|
|
43
|
+
# Default null sink for env[:events] — swallows anything pushed to it.
|
|
44
|
+
class NullSink
|
|
45
|
+
def <<(_event); self; end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
__END__
|
|
52
|
+
|
|
53
|
+
describe "brute/turn/pipeline" do
|
|
54
|
+
it "builds and calls a chain" do
|
|
55
|
+
seen = []
|
|
56
|
+
inc = Class.new do
|
|
57
|
+
def initialize(app, label:); @app = app; @label = label; end
|
|
58
|
+
def call(env); env[:trace] << @label; @app.call(env); env[:trace] << "#{@label}-after"; end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
pipeline = Brute::Turn::Pipeline.new do
|
|
62
|
+
use inc, label: "outer"
|
|
63
|
+
use inc, label: "inner"
|
|
64
|
+
run ->(env) { env[:trace] << "core" }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
env = { trace: [] }
|
|
68
|
+
pipeline.call(env)
|
|
69
|
+
env[:trace].should == ["outer", "inner", "core", "inner-after", "outer-after"]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "raises when run was never called" do
|
|
73
|
+
lambda { Brute::Turn::Pipeline.new.call({}) }.should.raise(RuntimeError)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "accepts a callable as the terminal app" do
|
|
77
|
+
pipeline = Brute::Turn::Pipeline.new do
|
|
78
|
+
run ->(env) { env[:result] = 42 }
|
|
79
|
+
end
|
|
80
|
+
env = {}
|
|
81
|
+
pipeline.call(env)
|
|
82
|
+
env[:result].should == 42
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "run accepts a block terminal and stays chainable" do
|
|
86
|
+
pipeline = Brute::Turn::Pipeline.new.use(Class.new do
|
|
87
|
+
def initialize(app); @app = app; end
|
|
88
|
+
def call(env); env[:trace] << "mw"; @app.call(env); end
|
|
89
|
+
end).run { |env| env[:trace] << "core" }
|
|
90
|
+
|
|
91
|
+
env = { trace: [] }
|
|
92
|
+
pipeline.call(env)
|
|
93
|
+
env[:trace].should == ["mw", "core"]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "brute"
|
|
5
|
+
require "brute/turn/pipeline"
|
|
6
|
+
|
|
7
|
+
module Brute
|
|
8
|
+
module Turn
|
|
9
|
+
# A ToolPipeline runs a tool call through a middleware stack. Like
|
|
10
|
+
# AgentPipeline it *composes* a Pipeline rather than inheriting: the
|
|
11
|
+
# definition block is instance_eval'd into the internal Pipeline, so `use`
|
|
12
|
+
# / `run` inside it are the builder's methods. The tool's terminal app does
|
|
13
|
+
# the work; middleware wraps it with concerns like file mutation queueing,
|
|
14
|
+
# validation, logging.
|
|
15
|
+
#
|
|
16
|
+
# Coexists with Brute::Tools::* (which inherit from Brute::Tool). Use a
|
|
17
|
+
# ToolPipeline when you want middleware; use Brute::Tool subclasses for
|
|
18
|
+
# simple cases.
|
|
19
|
+
#
|
|
20
|
+
# read = Brute::Turn::ToolPipeline.new(
|
|
21
|
+
# name: "read",
|
|
22
|
+
# description: "Read a file's contents",
|
|
23
|
+
# params: { file_path: { type: "string", required: true } },
|
|
24
|
+
# ) do
|
|
25
|
+
# use Brute::Middleware::Tool::ValidateParams
|
|
26
|
+
# run ->(env) {
|
|
27
|
+
# env[:result] = File.read(File.expand_path(env[:arguments][:file_path]))
|
|
28
|
+
# }
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# read.call(file_path: "lib/brute.rb")
|
|
32
|
+
#
|
|
33
|
+
class ToolPipeline
|
|
34
|
+
attr_reader :name, :description, :params
|
|
35
|
+
|
|
36
|
+
def initialize(name:, description:, params: {}, &block)
|
|
37
|
+
@name = name.to_s
|
|
38
|
+
@description = description
|
|
39
|
+
@params = params
|
|
40
|
+
@pipeline = Pipeline.new
|
|
41
|
+
@pipeline.instance_eval(&block) if block
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def call(events: Pipeline::NullSink.new, **arguments)
|
|
45
|
+
env = {
|
|
46
|
+
name: @name,
|
|
47
|
+
arguments: arguments,
|
|
48
|
+
result: nil,
|
|
49
|
+
events: events,
|
|
50
|
+
metadata: {},
|
|
51
|
+
}
|
|
52
|
+
@pipeline.call(env)
|
|
53
|
+
env[:result]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
__END__
|
|
61
|
+
|
|
62
|
+
describe "brute/turn/tool_pipeline" do
|
|
63
|
+
it "exposes name, description, params" do
|
|
64
|
+
t = Brute::Turn::ToolPipeline.new(name: "echo", description: "echo input") do
|
|
65
|
+
run ->(env) { env[:result] = env[:arguments][:msg] }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
t.name.should == "echo"
|
|
69
|
+
t.description.should == "echo input"
|
|
70
|
+
t.call(msg: "hi").should == "hi"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "passes arguments through env to the terminal app" do
|
|
74
|
+
captured = nil
|
|
75
|
+
t = Brute::Turn::ToolPipeline.new(name: "x", description: "x") do
|
|
76
|
+
run ->(env) { captured = env[:arguments]; env[:result] = nil }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
t.call(a: 1, b: 2)
|
|
80
|
+
captured.should == { a: 1, b: 2 }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "runs middleware around the terminal app (instance_eval'd block)" do
|
|
84
|
+
wrap = Class.new do
|
|
85
|
+
def initialize(app, tag:); @app = app; @tag = tag; end
|
|
86
|
+
def call(env); (env[:metadata][:log] ||= []) << "in-#{@tag}"; @app.call(env); env[:metadata][:log] << "out-#{@tag}"; end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
t = Brute::Turn::ToolPipeline.new(name: "x", description: "x") do
|
|
90
|
+
use wrap, tag: "a"
|
|
91
|
+
run ->(env) { env[:metadata][:log] << "core"; env[:result] = :ok }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
t.call(input: 1).should == :ok
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/brute/utils/diff.rb
CHANGED
data/lib/brute/version.rb
CHANGED
data/lib/brute.rb
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
|
|
4
5
|
require "rack"
|
|
5
6
|
require 'timeout'
|
|
6
7
|
require 'logger'
|
|
7
|
-
require 'scampi/kernel_ext'
|
|
8
8
|
require 'colorize_extended'
|
|
9
9
|
require 'active_support/all'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
# Cross-cutting concerns are implemented as Rack-style middleware in a
|
|
14
|
-
# Pipeline that wraps every LLM call:
|
|
15
|
-
#
|
|
16
|
-
# Tracing → Retry → Session → Tokens → Compaction → ToolErrors → DoomLoop → Reasoning → [LLM Call]
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
require 'brute/version'
|
|
11
|
+
require_relative 'brute/version'
|
|
20
12
|
|
|
21
13
|
module Brute
|
|
22
14
|
LOGO = <<-LOGO
|
|
@@ -29,70 +21,51 @@ module Brute
|
|
|
29
21
|
`Y8bod8P' d888b `V88V"V8P' "888" `Y8bod8P'
|
|
30
22
|
LOGO
|
|
31
23
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
config.azure_api_base = ENV['AZURE_API_BASE'] # Microsoft Foundry project endpoint
|
|
41
|
-
config.azure_api_key = ENV['AZURE_API_KEY'] # use this or
|
|
42
|
-
config.azure_ai_auth_token = ENV['AZURE_AI_AUTH_TOKEN'] # this
|
|
43
|
-
|
|
44
|
-
# Bedrock
|
|
45
|
-
config.bedrock_api_key = ENV['AWS_ACCESS_KEY_ID']
|
|
46
|
-
config.bedrock_secret_key = ENV['AWS_SECRET_ACCESS_KEY']
|
|
47
|
-
config.bedrock_region = ENV['AWS_REGION'] # Required for Bedrock
|
|
48
|
-
config.bedrock_session_token = ENV['AWS_SESSION_TOKEN'] # For temporary credentials
|
|
49
|
-
|
|
50
|
-
# DeepSeek
|
|
51
|
-
config.deepseek_api_key = ENV['DEEPSEEK_API_KEY']
|
|
52
|
-
config.deepseek_api_base = ENV['DEEPSEEK_API_BASE'] # Available in v1.13.0+ (optional custom DeepSeek endpoint)
|
|
53
|
-
|
|
54
|
-
# Gemini
|
|
55
|
-
config.gemini_api_key = ENV['GEMINI_API_KEY']
|
|
56
|
-
config.gemini_api_base = ENV['GEMINI_API_BASE'] # Available in v1.9.0+ (optional API version override)
|
|
57
|
-
|
|
58
|
-
# GPUStack
|
|
59
|
-
config.gpustack_api_base = ENV['GPUSTACK_API_BASE']
|
|
60
|
-
config.gpustack_api_key = ENV['GPUSTACK_API_KEY']
|
|
61
|
-
|
|
62
|
-
# Mistral
|
|
63
|
-
config.mistral_api_key = ENV['MISTRAL_API_KEY']
|
|
64
|
-
|
|
65
|
-
# Ollama
|
|
66
|
-
config.ollama_api_base = 'http://localhost:11434/v1'
|
|
67
|
-
config.ollama_api_key = ENV['OLLAMA_API_KEY'] # Available in v1.13.0+ (optional for authenticated/remote Ollama endpoints)
|
|
68
|
-
|
|
69
|
-
# OpenAI
|
|
70
|
-
config.openai_api_key = ENV['OPENAI_API_KEY']
|
|
71
|
-
config.openai_api_base = ENV['OPENAI_API_BASE'] # Optional custom OpenAI-compatible endpoint
|
|
72
|
-
|
|
73
|
-
# OpenRouter
|
|
74
|
-
config.openrouter_api_key = ENV['OPENROUTER_API_KEY']
|
|
75
|
-
config.openrouter_api_base = ENV['OPENROUTER_API_BASE'] # Available in v1.13.0+ (optional custom OpenRouter endpoint)
|
|
76
|
-
|
|
77
|
-
# Perplexity
|
|
78
|
-
config.perplexity_api_key = ENV['PERPLEXITY_API_KEY']
|
|
79
|
-
|
|
80
|
-
# Vertex AI
|
|
81
|
-
config.vertexai_project_id = ENV['GOOGLE_CLOUD_PROJECT'] # Available in v1.7.0+
|
|
82
|
-
config.vertexai_location = ENV['GOOGLE_CLOUD_LOCATION']
|
|
83
|
-
config.vertexai_service_account_key = ENV['VERTEXAI_SERVICE_ACCOUNT_KEY'] # Optional: service account JSON key
|
|
84
|
-
|
|
85
|
-
# xAI
|
|
86
|
-
config.xai_api_key = ENV['XAI_API_KEY'] # Available in v1.11.0+
|
|
87
|
-
end
|
|
88
|
-
RubyLLM.config
|
|
89
|
-
end
|
|
90
|
-
end
|
|
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.
|
|
91
32
|
|
|
92
33
|
def self.provider
|
|
93
34
|
@provider ||= :anthropic
|
|
94
35
|
end
|
|
95
|
-
|
|
36
|
+
|
|
37
|
+
# Start building an agent turn. Returns an AgentPipeline — a rack-style
|
|
38
|
+
# builder that is also the runnable Agent: chain `.use` for middleware and
|
|
39
|
+
# `.run` for the terminal LLM-call proc (both return the AgentPipeline), then
|
|
40
|
+
# invoke it with `#start`. It takes no config — LLM config lives in the `run`
|
|
41
|
+
# proc, tools go to the ToolPipeline middleware, the log to SessionLog.
|
|
42
|
+
#
|
|
43
|
+
# agent = Brute.agent
|
|
44
|
+
# .use(Brute::Middleware::SystemPrompt)
|
|
45
|
+
# .use(Brute::Middleware::ToolPipeline, tools: Brute::Tools::ALL)
|
|
46
|
+
# .run ->(env) { ... } # the LLM-call proc (provider/model/creds here)
|
|
47
|
+
#
|
|
48
|
+
# agent.start("what changed?")
|
|
49
|
+
#
|
|
50
|
+
# A block form is equivalent (evaluated in the AgentPipeline's context):
|
|
51
|
+
#
|
|
52
|
+
# Brute.agent do
|
|
53
|
+
# use Brute::Middleware::SystemPrompt
|
|
54
|
+
# run ->(env) { ... }
|
|
55
|
+
# end
|
|
56
|
+
def self.agent(&block)
|
|
57
|
+
Brute::Turn::AgentPipeline.new(&block)
|
|
58
|
+
end
|
|
59
|
+
|
|
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 || [])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
|
|
96
69
|
def self.provider=(p)
|
|
97
70
|
@provider = p.to_sym
|
|
98
71
|
end
|
|
@@ -102,3 +75,4 @@ Dir.glob("#{__dir__}/brute/**/*.rb").sort.each do |path|
|
|
|
102
75
|
require path
|
|
103
76
|
end
|
|
104
77
|
|
|
78
|
+
# gangsta g-dogg bruh...
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "brute"
|
|
5
|
+
|
|
3
6
|
require "shellwords"
|
|
4
7
|
|
|
5
|
-
module
|
|
8
|
+
module BruteCLI
|
|
6
9
|
module Providers
|
|
7
10
|
# A pseudo-LLM provider that executes user input as code via the
|
|
8
11
|
# existing Brute::Tools::Shell tool.
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require "brute"
|
|
6
|
-
end
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "brute"
|
|
7
5
|
|
|
8
6
|
require "securerandom"
|
|
9
7
|
|
|
10
|
-
module
|
|
8
|
+
module BruteCLI
|
|
11
9
|
module Providers
|
|
12
10
|
# Synthetic completion response returned by Brute::Providers::Shell.
|
|
13
11
|
#
|
|
@@ -30,15 +28,15 @@ module Brute
|
|
|
30
28
|
return [empty_assistant] if @command.nil?
|
|
31
29
|
|
|
32
30
|
call_id = "shell_#{SecureRandom.hex(8)}"
|
|
33
|
-
tool_calls =
|
|
34
|
-
|
|
31
|
+
tool_calls = [
|
|
32
|
+
Brute::ToolCall.new(
|
|
35
33
|
id: call_id,
|
|
36
34
|
name: "shell",
|
|
37
35
|
arguments: { "command" => @command },
|
|
38
36
|
)
|
|
39
|
-
|
|
37
|
+
]
|
|
40
38
|
|
|
41
|
-
[
|
|
39
|
+
[Brute::Message.new(
|
|
42
40
|
role: :assistant,
|
|
43
41
|
content: "",
|
|
44
42
|
tool_calls: tool_calls,
|
|
@@ -80,17 +78,13 @@ module Brute
|
|
|
80
78
|
end
|
|
81
79
|
|
|
82
80
|
def usage
|
|
83
|
-
|
|
84
|
-
input: 0,
|
|
85
|
-
output: 0,
|
|
86
|
-
reasoning: 0,
|
|
87
|
-
)
|
|
81
|
+
{ input: 0, output: 0, reasoning: 0 }
|
|
88
82
|
end
|
|
89
83
|
|
|
90
84
|
private
|
|
91
85
|
|
|
92
86
|
def empty_assistant
|
|
93
|
-
|
|
87
|
+
Brute::Message.new(role: :assistant, content: "")
|
|
94
88
|
end
|
|
95
89
|
end
|
|
96
90
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brute
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brute Contributors
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 1980-01-
|
|
10
|
+
date: 1980-01-01 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: async
|
|
@@ -38,7 +38,7 @@ dependencies:
|
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '1.5'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
41
|
+
name: activesupport
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
@@ -52,7 +52,7 @@ dependencies:
|
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
|
-
name:
|
|
55
|
+
name: colorize-extended
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - ">="
|
|
@@ -66,21 +66,21 @@ dependencies:
|
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
|
-
name:
|
|
69
|
+
name: rack
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
|
-
- - "
|
|
72
|
+
- - "~>"
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '0'
|
|
74
|
+
version: '3.0'
|
|
75
75
|
type: :runtime
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
|
-
- - "
|
|
79
|
+
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: '0'
|
|
81
|
+
version: '3.0'
|
|
82
82
|
- !ruby/object:Gem::Dependency
|
|
83
|
-
name:
|
|
83
|
+
name: net-http-persistent
|
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
|
85
85
|
requirements:
|
|
86
86
|
- - ">="
|
|
@@ -94,75 +94,75 @@ dependencies:
|
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '0'
|
|
96
96
|
- !ruby/object:Gem::Dependency
|
|
97
|
-
name:
|
|
97
|
+
name: json_schemer
|
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
|
99
99
|
requirements:
|
|
100
100
|
- - "~>"
|
|
101
101
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '
|
|
102
|
+
version: '2.5'
|
|
103
103
|
type: :runtime
|
|
104
104
|
prerelease: false
|
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
106
|
requirements:
|
|
107
107
|
- - "~>"
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '
|
|
109
|
+
version: '2.5'
|
|
110
110
|
- !ruby/object:Gem::Dependency
|
|
111
|
-
name:
|
|
111
|
+
name: google-protobuf
|
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
|
113
113
|
requirements:
|
|
114
|
-
- - "
|
|
114
|
+
- - "~>"
|
|
115
115
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: '
|
|
116
|
+
version: '4.34'
|
|
117
117
|
type: :runtime
|
|
118
118
|
prerelease: false
|
|
119
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
120
120
|
requirements:
|
|
121
|
-
- - "
|
|
121
|
+
- - "~>"
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: '
|
|
123
|
+
version: '4.34'
|
|
124
124
|
- !ruby/object:Gem::Dependency
|
|
125
|
-
name:
|
|
125
|
+
name: rake
|
|
126
126
|
requirement: !ruby/object:Gem::Requirement
|
|
127
127
|
requirements:
|
|
128
128
|
- - "~>"
|
|
129
129
|
- !ruby/object:Gem::Version
|
|
130
|
-
version: '
|
|
131
|
-
type: :
|
|
130
|
+
version: '13.0'
|
|
131
|
+
type: :development
|
|
132
132
|
prerelease: false
|
|
133
133
|
version_requirements: !ruby/object:Gem::Requirement
|
|
134
134
|
requirements:
|
|
135
135
|
- - "~>"
|
|
136
136
|
- !ruby/object:Gem::Version
|
|
137
|
-
version: '
|
|
137
|
+
version: '13.0'
|
|
138
138
|
- !ruby/object:Gem::Dependency
|
|
139
|
-
name:
|
|
139
|
+
name: scampi
|
|
140
140
|
requirement: !ruby/object:Gem::Requirement
|
|
141
141
|
requirements:
|
|
142
142
|
- - "~>"
|
|
143
143
|
- !ruby/object:Gem::Version
|
|
144
|
-
version: '
|
|
145
|
-
type: :
|
|
144
|
+
version: '1.0'
|
|
145
|
+
type: :development
|
|
146
146
|
prerelease: false
|
|
147
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
148
148
|
requirements:
|
|
149
149
|
- - "~>"
|
|
150
150
|
- !ruby/object:Gem::Version
|
|
151
|
-
version: '
|
|
151
|
+
version: '1.0'
|
|
152
152
|
- !ruby/object:Gem::Dependency
|
|
153
|
-
name:
|
|
153
|
+
name: lefthook
|
|
154
154
|
requirement: !ruby/object:Gem::Requirement
|
|
155
155
|
requirements:
|
|
156
156
|
- - "~>"
|
|
157
157
|
- !ruby/object:Gem::Version
|
|
158
|
-
version: '
|
|
158
|
+
version: '2.1'
|
|
159
159
|
type: :development
|
|
160
160
|
prerelease: false
|
|
161
161
|
version_requirements: !ruby/object:Gem::Requirement
|
|
162
162
|
requirements:
|
|
163
163
|
- - "~>"
|
|
164
164
|
- !ruby/object:Gem::Version
|
|
165
|
-
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: []
|
|
@@ -170,26 +170,30 @@ extensions: []
|
|
|
170
170
|
extra_rdoc_files: []
|
|
171
171
|
files:
|
|
172
172
|
- lib/brute.rb
|
|
173
|
-
- lib/brute/agent.rb
|
|
174
173
|
- lib/brute/events/handler.rb
|
|
175
174
|
- lib/brute/events/prefixed_terminal_output.rb
|
|
176
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
|
|
181
|
+
- lib/brute/messages.rb
|
|
177
182
|
- lib/brute/middleware/001_otel_span.rb
|
|
178
|
-
- lib/brute/middleware/
|
|
183
|
+
- lib/brute/middleware/002_session_log.rb
|
|
179
184
|
- lib/brute/middleware/004_summarize.rb
|
|
180
185
|
- lib/brute/middleware/005_tracing.rb
|
|
186
|
+
- lib/brute/middleware/006_loop.rb
|
|
181
187
|
- lib/brute/middleware/010_max_iterations.rb
|
|
182
188
|
- lib/brute/middleware/015_otel_token_usage.rb
|
|
183
189
|
- lib/brute/middleware/020_system_prompt.rb
|
|
184
190
|
- lib/brute/middleware/040_compaction_check.rb
|
|
185
191
|
- lib/brute/middleware/060_questions.rb
|
|
186
|
-
- lib/brute/middleware/
|
|
192
|
+
- lib/brute/middleware/070_tool_pipeline.rb
|
|
187
193
|
- lib/brute/middleware/073_otel_tool_call.rb
|
|
188
194
|
- lib/brute/middleware/075_otel_tool_results.rb
|
|
189
|
-
- lib/brute/middleware/100_llm_call.rb
|
|
190
195
|
- lib/brute/middleware/event_handler.rb
|
|
191
196
|
- lib/brute/middleware/user_queue.rb
|
|
192
|
-
- lib/brute/pipeline.rb
|
|
193
197
|
- lib/brute/prompts.rb
|
|
194
198
|
- lib/brute/prompts/autonomy.rb
|
|
195
199
|
- lib/brute/prompts/base.rb
|
|
@@ -232,17 +236,14 @@ files:
|
|
|
232
236
|
- lib/brute/prompts/text/tool_usage/google.txt
|
|
233
237
|
- lib/brute/prompts/tone_and_style.rb
|
|
234
238
|
- lib/brute/prompts/tool_usage.rb
|
|
235
|
-
- lib/brute/
|
|
236
|
-
- lib/brute/providers/shell_response.rb
|
|
237
|
-
- lib/brute/queue/file_mutation_queue.rb
|
|
238
|
-
- lib/brute/session.rb
|
|
239
|
+
- lib/brute/rack/adapter.rb
|
|
239
240
|
- lib/brute/skill.rb
|
|
240
|
-
- lib/brute/store/snapshot_store.rb
|
|
241
|
-
- lib/brute/store/todo_store.rb
|
|
242
|
-
- lib/brute/sub_agent.rb
|
|
243
241
|
- lib/brute/system_prompt.rb
|
|
244
242
|
- lib/brute/tool.rb
|
|
245
243
|
- lib/brute/tools.rb
|
|
244
|
+
- lib/brute/tools/adapter.rb
|
|
245
|
+
- lib/brute/tools/fs/file_mutation_queue.rb
|
|
246
|
+
- lib/brute/tools/fs/snapshot_store.rb
|
|
246
247
|
- lib/brute/tools/fs_patch.rb
|
|
247
248
|
- lib/brute/tools/fs_read.rb
|
|
248
249
|
- lib/brute/tools/fs_remove.rb
|
|
@@ -252,11 +253,19 @@ files:
|
|
|
252
253
|
- lib/brute/tools/net_fetch.rb
|
|
253
254
|
- lib/brute/tools/question.rb
|
|
254
255
|
- lib/brute/tools/shell.rb
|
|
256
|
+
- lib/brute/tools/skill_load.rb
|
|
257
|
+
- lib/brute/tools/sub_agent.rb
|
|
258
|
+
- lib/brute/tools/todo_list/store.rb
|
|
255
259
|
- lib/brute/tools/todo_read.rb
|
|
256
260
|
- lib/brute/tools/todo_write.rb
|
|
257
261
|
- lib/brute/truncation.rb
|
|
262
|
+
- lib/brute/turn/agent_pipeline.rb
|
|
263
|
+
- lib/brute/turn/pipeline.rb
|
|
264
|
+
- lib/brute/turn/tool_pipeline.rb
|
|
258
265
|
- lib/brute/utils/diff.rb
|
|
259
266
|
- lib/brute/version.rb
|
|
267
|
+
- lib/brute_cli/providers/shell.rb
|
|
268
|
+
- lib/brute_cli/providers/shell_response.rb
|
|
260
269
|
homepage: https://github.com/general-intelligence-systems/brute
|
|
261
270
|
licenses:
|
|
262
271
|
- MIT
|
|
@@ -276,7 +285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
276
285
|
- !ruby/object:Gem::Version
|
|
277
286
|
version: '0'
|
|
278
287
|
requirements: []
|
|
279
|
-
rubygems_version:
|
|
288
|
+
rubygems_version: 3.7.2
|
|
280
289
|
specification_version: 4
|
|
281
|
-
summary: A coding agent
|
|
290
|
+
summary: A framework-agnostic coding agent
|
|
282
291
|
test_files: []
|