agent-harness 0.2.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 +7 -0
- data/.markdownlint.yml +6 -0
- data/.markdownlintignore +8 -0
- data/.release-please-manifest.json +3 -0
- data/.rspec +3 -0
- data/.simplecov +26 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +27 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +274 -0
- data/Rakefile +103 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/lib/agent_harness/command_executor.rb +146 -0
- data/lib/agent_harness/configuration.rb +299 -0
- data/lib/agent_harness/error_taxonomy.rb +128 -0
- data/lib/agent_harness/errors.rb +63 -0
- data/lib/agent_harness/orchestration/circuit_breaker.rb +169 -0
- data/lib/agent_harness/orchestration/conductor.rb +179 -0
- data/lib/agent_harness/orchestration/health_monitor.rb +170 -0
- data/lib/agent_harness/orchestration/metrics.rb +167 -0
- data/lib/agent_harness/orchestration/provider_manager.rb +240 -0
- data/lib/agent_harness/orchestration/rate_limiter.rb +113 -0
- data/lib/agent_harness/providers/adapter.rb +163 -0
- data/lib/agent_harness/providers/aider.rb +109 -0
- data/lib/agent_harness/providers/anthropic.rb +345 -0
- data/lib/agent_harness/providers/base.rb +198 -0
- data/lib/agent_harness/providers/codex.rb +100 -0
- data/lib/agent_harness/providers/cursor.rb +281 -0
- data/lib/agent_harness/providers/gemini.rb +136 -0
- data/lib/agent_harness/providers/github_copilot.rb +155 -0
- data/lib/agent_harness/providers/kilocode.rb +73 -0
- data/lib/agent_harness/providers/opencode.rb +75 -0
- data/lib/agent_harness/providers/registry.rb +137 -0
- data/lib/agent_harness/response.rb +100 -0
- data/lib/agent_harness/token_tracker.rb +170 -0
- data/lib/agent_harness/version.rb +5 -0
- data/lib/agent_harness.rb +115 -0
- data/release-please-config.json +63 -0
- metadata +129 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "singleton"
|
|
4
|
+
|
|
5
|
+
module AgentHarness
|
|
6
|
+
module Providers
|
|
7
|
+
# Registry for provider classes
|
|
8
|
+
#
|
|
9
|
+
# Manages registration and lookup of provider classes. Supports dynamic
|
|
10
|
+
# registration of custom providers and aliasing of provider names.
|
|
11
|
+
#
|
|
12
|
+
# @example Registering a custom provider
|
|
13
|
+
# AgentHarness::Providers::Registry.instance.register(:my_provider, MyProviderClass)
|
|
14
|
+
#
|
|
15
|
+
# @example Looking up a provider
|
|
16
|
+
# klass = AgentHarness::Providers::Registry.instance.get(:claude)
|
|
17
|
+
class Registry
|
|
18
|
+
include Singleton
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@providers = {}
|
|
22
|
+
@aliases = {}
|
|
23
|
+
@builtin_registered = false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Register a provider class
|
|
27
|
+
#
|
|
28
|
+
# @param name [Symbol, String] the provider name
|
|
29
|
+
# @param klass [Class] the provider class
|
|
30
|
+
# @param aliases [Array<Symbol, String>] alternative names
|
|
31
|
+
# @return [void]
|
|
32
|
+
def register(name, klass, aliases: [])
|
|
33
|
+
name = name.to_sym
|
|
34
|
+
validate_provider_class!(klass)
|
|
35
|
+
|
|
36
|
+
@providers[name] = klass
|
|
37
|
+
|
|
38
|
+
aliases.each do |alias_name|
|
|
39
|
+
@aliases[alias_name.to_sym] = name
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
AgentHarness.logger&.debug("[AgentHarness::Registry] Registered provider: #{name}")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get provider class by name
|
|
46
|
+
#
|
|
47
|
+
# @param name [Symbol, String] the provider name
|
|
48
|
+
# @return [Class] the provider class
|
|
49
|
+
# @raise [ConfigurationError] if provider not found
|
|
50
|
+
def get(name)
|
|
51
|
+
ensure_builtin_providers_registered
|
|
52
|
+
name = resolve_alias(name.to_sym)
|
|
53
|
+
@providers[name] || raise(ConfigurationError, "Unknown provider: #{name}")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check if provider is registered
|
|
57
|
+
#
|
|
58
|
+
# @param name [Symbol, String] the provider name
|
|
59
|
+
# @return [Boolean] true if registered
|
|
60
|
+
def registered?(name)
|
|
61
|
+
ensure_builtin_providers_registered
|
|
62
|
+
name = resolve_alias(name.to_sym)
|
|
63
|
+
@providers.key?(name)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# List all registered provider names
|
|
67
|
+
#
|
|
68
|
+
# @return [Array<Symbol>] provider names
|
|
69
|
+
def all
|
|
70
|
+
ensure_builtin_providers_registered
|
|
71
|
+
@providers.keys
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# List available providers (CLI installed)
|
|
75
|
+
#
|
|
76
|
+
# @return [Array<Symbol>] available provider names
|
|
77
|
+
def available
|
|
78
|
+
ensure_builtin_providers_registered
|
|
79
|
+
@providers.select { |_, klass| klass.available? }.keys
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Reset registry (useful for testing)
|
|
83
|
+
#
|
|
84
|
+
# @return [void]
|
|
85
|
+
def reset!
|
|
86
|
+
@providers.clear
|
|
87
|
+
@aliases.clear
|
|
88
|
+
@builtin_registered = false
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def resolve_alias(name)
|
|
94
|
+
@aliases[name] || name
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def validate_provider_class!(klass)
|
|
98
|
+
includes_adapter = klass.included_modules.include?(Adapter)
|
|
99
|
+
has_required_methods = klass.respond_to?(:provider_name) &&
|
|
100
|
+
klass.respond_to?(:available?) &&
|
|
101
|
+
klass.respond_to?(:binary_name)
|
|
102
|
+
|
|
103
|
+
return if includes_adapter || has_required_methods
|
|
104
|
+
|
|
105
|
+
raise ConfigurationError, "Provider class must include AgentHarness::Providers::Adapter or implement required class methods"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def ensure_builtin_providers_registered
|
|
109
|
+
return if @builtin_registered
|
|
110
|
+
|
|
111
|
+
register_builtin_providers
|
|
112
|
+
@builtin_registered = true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def register_builtin_providers
|
|
116
|
+
# Only register providers that exist
|
|
117
|
+
# These will be loaded on demand
|
|
118
|
+
register_if_available(:claude, "agent_harness/providers/anthropic", :Anthropic, aliases: [:anthropic])
|
|
119
|
+
register_if_available(:cursor, "agent_harness/providers/cursor", :Cursor)
|
|
120
|
+
register_if_available(:gemini, "agent_harness/providers/gemini", :Gemini)
|
|
121
|
+
register_if_available(:github_copilot, "agent_harness/providers/github_copilot", :GithubCopilot, aliases: [:copilot])
|
|
122
|
+
register_if_available(:codex, "agent_harness/providers/codex", :Codex)
|
|
123
|
+
register_if_available(:opencode, "agent_harness/providers/opencode", :Opencode)
|
|
124
|
+
register_if_available(:kilocode, "agent_harness/providers/kilocode", :Kilocode)
|
|
125
|
+
register_if_available(:aider, "agent_harness/providers/aider", :Aider)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def register_if_available(name, require_path, class_name, aliases: [])
|
|
129
|
+
require_relative require_path.sub("agent_harness/providers/", "")
|
|
130
|
+
klass = AgentHarness::Providers.const_get(class_name)
|
|
131
|
+
register(name, klass, aliases: aliases)
|
|
132
|
+
rescue LoadError, NameError => e
|
|
133
|
+
AgentHarness.logger&.debug("[AgentHarness::Registry] Provider #{name} not available: #{e.message}")
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AgentHarness
|
|
4
|
+
# Response object returned from provider send_message calls
|
|
5
|
+
#
|
|
6
|
+
# Contains the output, status, and metadata from a provider interaction.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# response = provider.send_message(prompt: "Hello")
|
|
10
|
+
# if response.success?
|
|
11
|
+
# puts response.output
|
|
12
|
+
# else
|
|
13
|
+
# puts "Error: #{response.error}"
|
|
14
|
+
# end
|
|
15
|
+
class Response
|
|
16
|
+
attr_reader :output, :exit_code, :duration, :provider, :model
|
|
17
|
+
attr_reader :tokens, :metadata, :error
|
|
18
|
+
|
|
19
|
+
# Create a new Response
|
|
20
|
+
#
|
|
21
|
+
# @param output [String] the output from the provider
|
|
22
|
+
# @param exit_code [Integer] the exit code (0 for success)
|
|
23
|
+
# @param duration [Float] execution duration in seconds
|
|
24
|
+
# @param provider [Symbol, String] the provider name
|
|
25
|
+
# @param model [String, nil] the model used
|
|
26
|
+
# @param tokens [Hash, nil] token usage information
|
|
27
|
+
# @param metadata [Hash] additional metadata
|
|
28
|
+
# @param error [String, nil] error message if failed
|
|
29
|
+
def initialize(output:, exit_code:, duration:, provider:, model: nil,
|
|
30
|
+
tokens: nil, metadata: {}, error: nil)
|
|
31
|
+
@output = output
|
|
32
|
+
@exit_code = exit_code
|
|
33
|
+
@duration = duration
|
|
34
|
+
@provider = provider.to_sym
|
|
35
|
+
@model = model
|
|
36
|
+
@tokens = tokens
|
|
37
|
+
@metadata = metadata
|
|
38
|
+
@error = error
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Check if the response indicates success
|
|
42
|
+
#
|
|
43
|
+
# @return [Boolean] true if exit_code is 0 and no error
|
|
44
|
+
def success?
|
|
45
|
+
@exit_code == 0 && @error.nil?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Check if the response indicates failure
|
|
49
|
+
#
|
|
50
|
+
# @return [Boolean] true if not successful
|
|
51
|
+
def failed?
|
|
52
|
+
!success?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get total tokens used
|
|
56
|
+
#
|
|
57
|
+
# @return [Integer, nil] total tokens or nil if not tracked
|
|
58
|
+
def total_tokens
|
|
59
|
+
@tokens&.[](:total)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get input tokens used
|
|
63
|
+
#
|
|
64
|
+
# @return [Integer, nil] input tokens or nil if not tracked
|
|
65
|
+
def input_tokens
|
|
66
|
+
@tokens&.[](:input)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get output tokens used
|
|
70
|
+
#
|
|
71
|
+
# @return [Integer, nil] output tokens or nil if not tracked
|
|
72
|
+
def output_tokens
|
|
73
|
+
@tokens&.[](:output)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Convert to hash representation
|
|
77
|
+
#
|
|
78
|
+
# @return [Hash] hash representation of the response
|
|
79
|
+
def to_h
|
|
80
|
+
{
|
|
81
|
+
output: @output,
|
|
82
|
+
exit_code: @exit_code,
|
|
83
|
+
duration: @duration,
|
|
84
|
+
provider: @provider,
|
|
85
|
+
model: @model,
|
|
86
|
+
tokens: @tokens,
|
|
87
|
+
metadata: @metadata,
|
|
88
|
+
error: @error,
|
|
89
|
+
success: success?
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# String representation for debugging
|
|
94
|
+
#
|
|
95
|
+
# @return [String] debug string
|
|
96
|
+
def inspect
|
|
97
|
+
"#<AgentHarness::Response provider=#{@provider} success=#{success?} duration=#{@duration.round(2)}s>"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module AgentHarness
|
|
6
|
+
# Tracks token usage across provider interactions
|
|
7
|
+
#
|
|
8
|
+
# Provides in-memory tracking of token usage with support for callbacks
|
|
9
|
+
# when tokens are used. Consumers can register callbacks to persist
|
|
10
|
+
# usage data externally.
|
|
11
|
+
#
|
|
12
|
+
# @example Basic usage
|
|
13
|
+
# tracker = AgentHarness::TokenTracker.new
|
|
14
|
+
# tracker.record(provider: :claude, input_tokens: 100, output_tokens: 50)
|
|
15
|
+
# puts tracker.summary
|
|
16
|
+
#
|
|
17
|
+
# @example With callback
|
|
18
|
+
# tracker.on_tokens_used do |event|
|
|
19
|
+
# MyDatabase.save_usage(event)
|
|
20
|
+
# end
|
|
21
|
+
class TokenTracker
|
|
22
|
+
# Token usage event structure
|
|
23
|
+
TokenEvent = Struct.new(
|
|
24
|
+
:provider, :model, :input_tokens, :output_tokens, :total_tokens,
|
|
25
|
+
:timestamp, :request_id,
|
|
26
|
+
keyword_init: true
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def initialize
|
|
30
|
+
@events = []
|
|
31
|
+
@callbacks = []
|
|
32
|
+
@mutex = Mutex.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Record token usage
|
|
36
|
+
#
|
|
37
|
+
# @param provider [Symbol, String] the provider name
|
|
38
|
+
# @param model [String, nil] the model used
|
|
39
|
+
# @param input_tokens [Integer] input tokens used
|
|
40
|
+
# @param output_tokens [Integer] output tokens used
|
|
41
|
+
# @param total_tokens [Integer, nil] total tokens (calculated if nil)
|
|
42
|
+
# @param request_id [String, nil] unique request ID (generated if nil)
|
|
43
|
+
# @return [TokenEvent] the recorded event
|
|
44
|
+
def record(provider:, model: nil, input_tokens: 0, output_tokens: 0, total_tokens: nil, request_id: nil)
|
|
45
|
+
total = total_tokens || (input_tokens + output_tokens)
|
|
46
|
+
|
|
47
|
+
event = TokenEvent.new(
|
|
48
|
+
provider: provider.to_sym,
|
|
49
|
+
model: model,
|
|
50
|
+
input_tokens: input_tokens,
|
|
51
|
+
output_tokens: output_tokens,
|
|
52
|
+
total_tokens: total,
|
|
53
|
+
timestamp: Time.now,
|
|
54
|
+
request_id: request_id || SecureRandom.uuid
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@mutex.synchronize do
|
|
58
|
+
@events << event
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Notify callbacks
|
|
62
|
+
notify_callbacks(event)
|
|
63
|
+
|
|
64
|
+
event
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Get usage summary
|
|
68
|
+
#
|
|
69
|
+
# @param since [Time, nil] only include events after this time
|
|
70
|
+
# @param provider [Symbol, String, nil] filter by provider
|
|
71
|
+
# @return [Hash] usage summary
|
|
72
|
+
def summary(since: nil, provider: nil)
|
|
73
|
+
events = filtered_events(since: since, provider: provider)
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
total_requests: events.size,
|
|
77
|
+
total_input_tokens: events.sum(&:input_tokens),
|
|
78
|
+
total_output_tokens: events.sum(&:output_tokens),
|
|
79
|
+
total_tokens: events.sum(&:total_tokens),
|
|
80
|
+
by_provider: group_by_provider(events),
|
|
81
|
+
by_model: group_by_model(events)
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Get recent events
|
|
86
|
+
#
|
|
87
|
+
# @param limit [Integer] maximum number of events to return
|
|
88
|
+
# @return [Array<TokenEvent>] recent events
|
|
89
|
+
def recent_events(limit: 100)
|
|
90
|
+
@mutex.synchronize do
|
|
91
|
+
@events.last(limit)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Register callback for token events
|
|
96
|
+
#
|
|
97
|
+
# @yield [TokenEvent] called when tokens are recorded
|
|
98
|
+
# @return [void]
|
|
99
|
+
def on_tokens_used(&block)
|
|
100
|
+
@callbacks << block
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Clear all recorded events
|
|
104
|
+
#
|
|
105
|
+
# @return [void]
|
|
106
|
+
def clear!
|
|
107
|
+
@mutex.synchronize do
|
|
108
|
+
@events.clear
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Get total token count
|
|
113
|
+
#
|
|
114
|
+
# @param since [Time, nil] only include events after this time
|
|
115
|
+
# @return [Integer] total tokens used
|
|
116
|
+
def total_tokens(since: nil)
|
|
117
|
+
filtered_events(since: since).sum(&:total_tokens)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Get event count
|
|
121
|
+
#
|
|
122
|
+
# @return [Integer] number of recorded events
|
|
123
|
+
def event_count
|
|
124
|
+
@mutex.synchronize do
|
|
125
|
+
@events.size
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def filtered_events(since: nil, provider: nil)
|
|
132
|
+
@mutex.synchronize do
|
|
133
|
+
events = @events.dup
|
|
134
|
+
events = events.select { |e| e.timestamp >= since } if since
|
|
135
|
+
events = events.select { |e| e.provider.to_s == provider.to_s } if provider
|
|
136
|
+
events
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def group_by_provider(events)
|
|
141
|
+
events.group_by(&:provider).transform_values do |provider_events|
|
|
142
|
+
{
|
|
143
|
+
requests: provider_events.size,
|
|
144
|
+
input_tokens: provider_events.sum(&:input_tokens),
|
|
145
|
+
output_tokens: provider_events.sum(&:output_tokens),
|
|
146
|
+
total_tokens: provider_events.sum(&:total_tokens)
|
|
147
|
+
}
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def group_by_model(events)
|
|
152
|
+
events.group_by { |e| "#{e.provider}:#{e.model}" }.transform_values do |model_events|
|
|
153
|
+
{
|
|
154
|
+
requests: model_events.size,
|
|
155
|
+
input_tokens: model_events.sum(&:input_tokens),
|
|
156
|
+
output_tokens: model_events.sum(&:output_tokens),
|
|
157
|
+
total_tokens: model_events.sum(&:total_tokens)
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def notify_callbacks(event)
|
|
163
|
+
@callbacks.each do |callback|
|
|
164
|
+
callback.call(event)
|
|
165
|
+
rescue => e
|
|
166
|
+
AgentHarness.logger&.error("[AgentHarness::TokenTracker] Callback error: #{e.message}")
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "agent_harness/version"
|
|
4
|
+
|
|
5
|
+
# AgentHarness provides a unified interface for CLI-based AI coding agents.
|
|
6
|
+
#
|
|
7
|
+
# It offers:
|
|
8
|
+
# - Unified interface for multiple AI coding agents (Claude Code, Cursor, Gemini CLI, etc.)
|
|
9
|
+
# - Full orchestration layer with provider switching, circuit breakers, and health monitoring
|
|
10
|
+
# - Flexible configuration via YAML, Ruby DSL, or environment variables
|
|
11
|
+
# - Dynamic provider registration for custom provider support
|
|
12
|
+
# - Token usage tracking for cost and limit calculations
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# AgentHarness.send_message("Write a hello world function", provider: :claude)
|
|
16
|
+
#
|
|
17
|
+
# @example With configuration
|
|
18
|
+
# AgentHarness.configure do |config|
|
|
19
|
+
# config.logger = Logger.new(STDOUT)
|
|
20
|
+
# config.default_provider = :cursor
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @example Direct provider access
|
|
24
|
+
# provider = AgentHarness.provider(:claude)
|
|
25
|
+
# provider.send_message(prompt: "Hello")
|
|
26
|
+
#
|
|
27
|
+
module AgentHarness
|
|
28
|
+
class Error < StandardError; end
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
# Returns the global configuration instance
|
|
32
|
+
# @return [Configuration] the configuration object
|
|
33
|
+
def configuration
|
|
34
|
+
@configuration ||= Configuration.new
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Configure AgentHarness with a block
|
|
38
|
+
# @yield [Configuration] the configuration object
|
|
39
|
+
# @return [void]
|
|
40
|
+
def configure
|
|
41
|
+
yield(configuration) if block_given?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Reset configuration to defaults (useful for testing)
|
|
45
|
+
# @return [void]
|
|
46
|
+
def reset!
|
|
47
|
+
@configuration = nil
|
|
48
|
+
@conductor = nil
|
|
49
|
+
@token_tracker = nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns the global logger
|
|
53
|
+
# @return [Logger, nil] the configured logger
|
|
54
|
+
def logger
|
|
55
|
+
configuration.logger
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the global token tracker
|
|
59
|
+
# @return [TokenTracker] the token tracker instance
|
|
60
|
+
def token_tracker
|
|
61
|
+
@token_tracker ||= TokenTracker.new
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns the global conductor for orchestrated requests
|
|
65
|
+
# @return [Orchestration::Conductor] the conductor instance
|
|
66
|
+
def conductor
|
|
67
|
+
@conductor ||= Orchestration::Conductor.new(config: configuration)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Send a message using the orchestration layer
|
|
71
|
+
# @param prompt [String] the prompt to send
|
|
72
|
+
# @param provider [Symbol, nil] optional provider override
|
|
73
|
+
# @param options [Hash] additional options
|
|
74
|
+
# @return [Response] the response from the provider
|
|
75
|
+
def send_message(prompt, provider: nil, **options)
|
|
76
|
+
conductor.send_message(prompt, provider: provider, **options)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get a provider instance
|
|
80
|
+
# @param name [Symbol] the provider name
|
|
81
|
+
# @return [Providers::Base] the provider instance
|
|
82
|
+
def provider(name)
|
|
83
|
+
conductor.provider_manager.get_provider(name)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Core components
|
|
89
|
+
require_relative "agent_harness/errors"
|
|
90
|
+
require_relative "agent_harness/configuration"
|
|
91
|
+
require_relative "agent_harness/command_executor"
|
|
92
|
+
require_relative "agent_harness/response"
|
|
93
|
+
require_relative "agent_harness/token_tracker"
|
|
94
|
+
require_relative "agent_harness/error_taxonomy"
|
|
95
|
+
|
|
96
|
+
# Provider layer
|
|
97
|
+
require_relative "agent_harness/providers/registry"
|
|
98
|
+
require_relative "agent_harness/providers/adapter"
|
|
99
|
+
require_relative "agent_harness/providers/base"
|
|
100
|
+
require_relative "agent_harness/providers/anthropic"
|
|
101
|
+
require_relative "agent_harness/providers/aider"
|
|
102
|
+
require_relative "agent_harness/providers/codex"
|
|
103
|
+
require_relative "agent_harness/providers/cursor"
|
|
104
|
+
require_relative "agent_harness/providers/gemini"
|
|
105
|
+
require_relative "agent_harness/providers/github_copilot"
|
|
106
|
+
require_relative "agent_harness/providers/kilocode"
|
|
107
|
+
require_relative "agent_harness/providers/opencode"
|
|
108
|
+
|
|
109
|
+
# Orchestration layer
|
|
110
|
+
require_relative "agent_harness/orchestration/circuit_breaker"
|
|
111
|
+
require_relative "agent_harness/orchestration/rate_limiter"
|
|
112
|
+
require_relative "agent_harness/orchestration/health_monitor"
|
|
113
|
+
require_relative "agent_harness/orchestration/metrics"
|
|
114
|
+
require_relative "agent_harness/orchestration/provider_manager"
|
|
115
|
+
require_relative "agent_harness/orchestration/conductor"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
|
3
|
+
"packages": {
|
|
4
|
+
".": {
|
|
5
|
+
"release-type": "ruby",
|
|
6
|
+
"package-name": "agent-harness",
|
|
7
|
+
"version-file": "lib/agent_harness/version.rb",
|
|
8
|
+
"extra-files": ["Gemfile.lock"],
|
|
9
|
+
"changelog-sections": [
|
|
10
|
+
{
|
|
11
|
+
"type": "feat",
|
|
12
|
+
"section": "Features",
|
|
13
|
+
"hidden": false
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "fix",
|
|
17
|
+
"section": "Bug Fixes",
|
|
18
|
+
"hidden": false
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"type": "refactor",
|
|
22
|
+
"section": "Improvements",
|
|
23
|
+
"hidden": false
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "chore",
|
|
27
|
+
"section": "Maintenance",
|
|
28
|
+
"hidden": true
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "docs",
|
|
32
|
+
"section": "Documentation",
|
|
33
|
+
"hidden": false
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"type": "test",
|
|
37
|
+
"section": "Testing",
|
|
38
|
+
"hidden": true
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "spec",
|
|
42
|
+
"section": "Test Specifications",
|
|
43
|
+
"hidden": true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"type": "ci",
|
|
47
|
+
"section": "CI/CD",
|
|
48
|
+
"hidden": true
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"type": "deps",
|
|
52
|
+
"section": "Dependencies",
|
|
53
|
+
"hidden": false
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"type": "BREAKING CHANGE",
|
|
57
|
+
"section": "Breaking Changes",
|
|
58
|
+
"hidden": false
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|