agent-harness 0.5.5 → 0.5.7
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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +24 -0
- data/README.md +76 -1
- data/lib/agent_harness/command_executor.rb +453 -32
- data/lib/agent_harness/docker_command_executor.rb +23 -3
- data/lib/agent_harness/error_taxonomy.rb +10 -0
- data/lib/agent_harness/errors.rb +5 -0
- data/lib/agent_harness/orchestration/conductor.rb +40 -16
- data/lib/agent_harness/orchestration/provider_manager.rb +21 -13
- data/lib/agent_harness/provider_health_check.rb +216 -58
- data/lib/agent_harness/provider_runtime.rb +132 -0
- data/lib/agent_harness/providers/adapter.rb +157 -0
- data/lib/agent_harness/providers/aider.rb +21 -0
- data/lib/agent_harness/providers/anthropic.rb +21 -0
- data/lib/agent_harness/providers/base.rb +83 -11
- data/lib/agent_harness/providers/codex.rb +75 -8
- data/lib/agent_harness/providers/cursor.rb +47 -2
- data/lib/agent_harness/providers/gemini.rb +53 -0
- data/lib/agent_harness/providers/github_copilot.rb +34 -6
- data/lib/agent_harness/providers/kilocode.rb +39 -0
- data/lib/agent_harness/providers/mistral_vibe.rb +4 -0
- data/lib/agent_harness/providers/opencode.rb +91 -1
- data/lib/agent_harness/providers/registry.rb +54 -0
- data/lib/agent_harness/version.rb +1 -1
- data/lib/agent_harness.rb +78 -6
- metadata +22 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require "rubygems/requirement"
|
|
4
5
|
require "time"
|
|
5
6
|
|
|
6
7
|
module AgentHarness
|
|
@@ -11,6 +12,9 @@ module AgentHarness
|
|
|
11
12
|
class Gemini < Base
|
|
12
13
|
# Model name pattern for Gemini models
|
|
13
14
|
MODEL_PATTERN = /^gemini-[\d.]+-(?:pro|flash|ultra)(?:-\d+)?$/i
|
|
15
|
+
CLI_PACKAGE = "@google/gemini-cli"
|
|
16
|
+
SUPPORTED_CLI_VERSION = "0.35.3"
|
|
17
|
+
SUPPORTED_CLI_REQUIREMENT = Gem::Requirement.new("= #{SUPPORTED_CLI_VERSION}").freeze
|
|
14
18
|
|
|
15
19
|
class << self
|
|
16
20
|
def provider_name
|
|
@@ -26,6 +30,32 @@ module AgentHarness
|
|
|
26
30
|
!!executor.which(binary_name)
|
|
27
31
|
end
|
|
28
32
|
|
|
33
|
+
def install_contract(version: SUPPORTED_CLI_VERSION)
|
|
34
|
+
parsed_version = begin
|
|
35
|
+
Gem::Version.new(version)
|
|
36
|
+
rescue ArgumentError
|
|
37
|
+
raise ArgumentError, "Unsupported Gemini CLI version #{version.inspect}. Supported requirement: #{SUPPORTED_CLI_REQUIREMENT}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
unless SUPPORTED_CLI_REQUIREMENT.satisfied_by?(parsed_version)
|
|
41
|
+
raise ArgumentError, "Unsupported Gemini CLI version #{version.inspect}. Supported requirement: #{SUPPORTED_CLI_REQUIREMENT}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
package_spec = "#{CLI_PACKAGE}@#{version}"
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
provider: provider_name,
|
|
48
|
+
source_type: :npm,
|
|
49
|
+
package_name: CLI_PACKAGE,
|
|
50
|
+
supported_version_requirement: SUPPORTED_CLI_REQUIREMENT,
|
|
51
|
+
default_version: SUPPORTED_CLI_VERSION,
|
|
52
|
+
resolved_version: version,
|
|
53
|
+
binary_name: binary_name,
|
|
54
|
+
install_command: ["npm", "install", "-g", "--ignore-scripts", package_spec],
|
|
55
|
+
install_command_string: "npm install -g --ignore-scripts #{package_spec}"
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
29
59
|
def firewall_requirements
|
|
30
60
|
{
|
|
31
61
|
domains: [
|
|
@@ -73,6 +103,10 @@ module AgentHarness
|
|
|
73
103
|
def supports_model_family?(family_name)
|
|
74
104
|
MODEL_PATTERN.match?(family_name) || family_name.start_with?("gemini-")
|
|
75
105
|
end
|
|
106
|
+
|
|
107
|
+
def smoke_test_contract
|
|
108
|
+
Base::DEFAULT_SMOKE_TEST_CONTRACT
|
|
109
|
+
end
|
|
76
110
|
end
|
|
77
111
|
|
|
78
112
|
def name
|
|
@@ -83,6 +117,25 @@ module AgentHarness
|
|
|
83
117
|
"Google Gemini"
|
|
84
118
|
end
|
|
85
119
|
|
|
120
|
+
def configuration_schema
|
|
121
|
+
{
|
|
122
|
+
fields: [
|
|
123
|
+
{
|
|
124
|
+
name: :model,
|
|
125
|
+
type: :string,
|
|
126
|
+
label: "Model",
|
|
127
|
+
required: false,
|
|
128
|
+
hint: "Gemini model to use (e.g. gemini-2.5-pro, gemini-2.0-flash)",
|
|
129
|
+
# accepts_arbitrary is true because supports_model_family? accepts
|
|
130
|
+
# any string starting with "gemini-", not just discovered models.
|
|
131
|
+
accepts_arbitrary: true
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
auth_modes: [:api_key, :oauth],
|
|
135
|
+
openai_compatible: false
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
86
139
|
def capabilities
|
|
87
140
|
{
|
|
88
141
|
streaming: true,
|
|
@@ -9,13 +9,27 @@ module AgentHarness
|
|
|
9
9
|
# Model name pattern for GitHub Copilot (uses OpenAI models)
|
|
10
10
|
MODEL_PATTERN = /^gpt-[\d.o-]+(?:-turbo)?(?:-mini)?$/i
|
|
11
11
|
|
|
12
|
+
# Copilot-specific smoke test contract. The `what-the-shell` subcommand
|
|
13
|
+
# translates natural language into shell commands, so the generic
|
|
14
|
+
# "Reply with exactly OK." prompt would produce something like
|
|
15
|
+
# `echo "OK"` rather than the literal text "OK". We use a prompt that
|
|
16
|
+
# is meaningful for the shell-translation path and only require
|
|
17
|
+
# non-empty output (no exact match).
|
|
18
|
+
SMOKE_TEST_CONTRACT = {
|
|
19
|
+
prompt: "list files in the current directory",
|
|
20
|
+
expected_output: nil,
|
|
21
|
+
timeout: 30,
|
|
22
|
+
require_output: true,
|
|
23
|
+
success_message: "Smoke test passed"
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
12
26
|
class << self
|
|
13
27
|
def provider_name
|
|
14
28
|
:github_copilot
|
|
15
29
|
end
|
|
16
30
|
|
|
17
31
|
def binary_name
|
|
18
|
-
"copilot"
|
|
32
|
+
"github-copilot-cli"
|
|
19
33
|
end
|
|
20
34
|
|
|
21
35
|
def available?
|
|
@@ -56,6 +70,10 @@ module AgentHarness
|
|
|
56
70
|
]
|
|
57
71
|
end
|
|
58
72
|
|
|
73
|
+
def smoke_test_contract
|
|
74
|
+
SMOKE_TEST_CONTRACT
|
|
75
|
+
end
|
|
76
|
+
|
|
59
77
|
def model_family(provider_model_name)
|
|
60
78
|
provider_model_name
|
|
61
79
|
end
|
|
@@ -77,6 +95,14 @@ module AgentHarness
|
|
|
77
95
|
"GitHub Copilot CLI"
|
|
78
96
|
end
|
|
79
97
|
|
|
98
|
+
def configuration_schema
|
|
99
|
+
{
|
|
100
|
+
fields: [],
|
|
101
|
+
auth_modes: [:oauth],
|
|
102
|
+
openai_compatible: false
|
|
103
|
+
}
|
|
104
|
+
end
|
|
105
|
+
|
|
80
106
|
def capabilities
|
|
81
107
|
{
|
|
82
108
|
streaming: false,
|
|
@@ -108,10 +134,10 @@ module AgentHarness
|
|
|
108
134
|
|
|
109
135
|
def execution_semantics
|
|
110
136
|
{
|
|
111
|
-
prompt_delivery: :
|
|
137
|
+
prompt_delivery: :arg,
|
|
112
138
|
output_format: :text,
|
|
113
139
|
sandbox_aware: false,
|
|
114
|
-
uses_subcommand:
|
|
140
|
+
uses_subcommand: true,
|
|
115
141
|
non_interactive_flag: nil,
|
|
116
142
|
legitimate_exit_codes: [0],
|
|
117
143
|
stderr_is_diagnostic: true,
|
|
@@ -147,10 +173,12 @@ module AgentHarness
|
|
|
147
173
|
protected
|
|
148
174
|
|
|
149
175
|
def build_command(prompt, options)
|
|
150
|
-
cmd = [self.class.binary_name, "-
|
|
176
|
+
cmd = [self.class.binary_name, "what-the-shell", prompt]
|
|
151
177
|
|
|
152
|
-
#
|
|
153
|
-
|
|
178
|
+
# Opt in to unrestricted tool access explicitly to preserve a safe default.
|
|
179
|
+
if supports_dangerous_mode? && options[:dangerous_mode]
|
|
180
|
+
cmd += dangerous_mode_flags
|
|
181
|
+
end
|
|
154
182
|
|
|
155
183
|
# Add session support if provided
|
|
156
184
|
if options[:session] && !options[:session].empty?
|
|
@@ -6,6 +6,10 @@ module AgentHarness
|
|
|
6
6
|
#
|
|
7
7
|
# Provides integration with the Kilocode CLI tool.
|
|
8
8
|
class Kilocode < Base
|
|
9
|
+
PACKAGE_NAME = "@kilocode/cli"
|
|
10
|
+
DEFAULT_VERSION = "7.1.3"
|
|
11
|
+
SUPPORTED_VERSION_REQUIREMENT = "= #{DEFAULT_VERSION}"
|
|
12
|
+
|
|
9
13
|
class << self
|
|
10
14
|
def provider_name
|
|
11
15
|
:kilocode
|
|
@@ -35,6 +39,41 @@ module AgentHarness
|
|
|
35
39
|
return [] unless available?
|
|
36
40
|
[]
|
|
37
41
|
end
|
|
42
|
+
|
|
43
|
+
def installation_contract(version: DEFAULT_VERSION)
|
|
44
|
+
validate_install_version!(version)
|
|
45
|
+
package_spec = "#{PACKAGE_NAME}@#{version}"
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
source: {
|
|
49
|
+
type: :npm,
|
|
50
|
+
package: PACKAGE_NAME
|
|
51
|
+
},
|
|
52
|
+
install_command: ["npm", "install", "-g", "--ignore-scripts", package_spec],
|
|
53
|
+
binary_name: binary_name,
|
|
54
|
+
default_version: DEFAULT_VERSION,
|
|
55
|
+
supported_version_requirement: SUPPORTED_VERSION_REQUIREMENT
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def install_command(version: DEFAULT_VERSION)
|
|
60
|
+
installation_contract(version: version)[:install_command]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def smoke_test_contract
|
|
64
|
+
Base::DEFAULT_SMOKE_TEST_CONTRACT
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def validate_install_version!(version)
|
|
70
|
+
requirement = Gem::Requirement.new(SUPPORTED_VERSION_REQUIREMENT)
|
|
71
|
+
return if requirement.satisfied_by?(Gem::Version.new(version))
|
|
72
|
+
|
|
73
|
+
raise ArgumentError,
|
|
74
|
+
"Unsupported Kilocode CLI version #{version.inspect}; " \
|
|
75
|
+
"supported versions must satisfy #{SUPPORTED_VERSION_REQUIREMENT}"
|
|
76
|
+
end
|
|
38
77
|
end
|
|
39
78
|
|
|
40
79
|
def name
|
|
@@ -6,6 +6,15 @@ module AgentHarness
|
|
|
6
6
|
#
|
|
7
7
|
# Provides integration with the OpenCode CLI tool.
|
|
8
8
|
class Opencode < Base
|
|
9
|
+
CLI_PACKAGE = "opencode-ai"
|
|
10
|
+
SUPPORTED_CLI_VERSION = "1.3.2"
|
|
11
|
+
SUPPORTED_CLI_REQUIREMENT = Gem::Requirement.new(">= #{SUPPORTED_CLI_VERSION}", "< 1.4.0").freeze
|
|
12
|
+
INSTALL_COMMAND_PREFIX = ["npm", "install", "-g", "--ignore-scripts"].freeze
|
|
13
|
+
SUPPORTED_CLI_VERSIONS = [SUPPORTED_CLI_VERSION].freeze
|
|
14
|
+
VERSION_REQUIREMENT_STRINGS = SUPPORTED_CLI_REQUIREMENT.requirements
|
|
15
|
+
.map { |op, ver| "#{op} #{ver}".freeze }
|
|
16
|
+
.freeze
|
|
17
|
+
|
|
9
18
|
class << self
|
|
10
19
|
def provider_name
|
|
11
20
|
:opencode
|
|
@@ -37,8 +46,66 @@ module AgentHarness
|
|
|
37
46
|
return [] unless available?
|
|
38
47
|
[]
|
|
39
48
|
end
|
|
49
|
+
|
|
50
|
+
def installation_contract(version: SUPPORTED_CLI_VERSION)
|
|
51
|
+
normalized_version = normalize_install_version(version)
|
|
52
|
+
return DEFAULT_INSTALLATION_CONTRACT if normalized_version == SUPPORTED_CLI_VERSION
|
|
53
|
+
|
|
54
|
+
build_installation_contract(normalized_version)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def install_command(version: SUPPORTED_CLI_VERSION)
|
|
58
|
+
installation_contract(version: version)[:install_command]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def smoke_test_contract
|
|
62
|
+
Base::DEFAULT_SMOKE_TEST_CONTRACT
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def build_installation_contract(version)
|
|
68
|
+
package = "#{CLI_PACKAGE}@#{version}".freeze
|
|
69
|
+
install_command = (INSTALL_COMMAND_PREFIX + [package]).freeze
|
|
70
|
+
|
|
71
|
+
contract = {
|
|
72
|
+
source: :npm,
|
|
73
|
+
package: package,
|
|
74
|
+
package_name: CLI_PACKAGE,
|
|
75
|
+
version: version,
|
|
76
|
+
version_requirement: VERSION_REQUIREMENT_STRINGS,
|
|
77
|
+
binary_name: binary_name,
|
|
78
|
+
install_command_prefix: INSTALL_COMMAND_PREFIX,
|
|
79
|
+
install_command: install_command,
|
|
80
|
+
supported_versions: SUPPORTED_CLI_VERSIONS
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
contract.each_value do |value|
|
|
84
|
+
value.freeze if value.is_a?(String)
|
|
85
|
+
end
|
|
86
|
+
contract.freeze
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def normalize_install_version(version)
|
|
90
|
+
raise ArgumentError, unsupported_version_message(version) unless version.is_a?(String) && !version.strip.empty?
|
|
91
|
+
|
|
92
|
+
normalized_version = version.strip
|
|
93
|
+
parsed_version = Gem::Version.new(normalized_version)
|
|
94
|
+
return normalized_version if SUPPORTED_CLI_REQUIREMENT.satisfied_by?(parsed_version)
|
|
95
|
+
|
|
96
|
+
raise ArgumentError, unsupported_version_message(version)
|
|
97
|
+
rescue ArgumentError
|
|
98
|
+
raise ArgumentError, unsupported_version_message(version)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def unsupported_version_message(version)
|
|
102
|
+
"Unsupported OpenCode CLI version #{version.inspect}; " \
|
|
103
|
+
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
|
|
104
|
+
end
|
|
40
105
|
end
|
|
41
106
|
|
|
107
|
+
DEFAULT_INSTALLATION_CONTRACT = build_installation_contract(SUPPORTED_CLI_VERSION)
|
|
108
|
+
|
|
42
109
|
def name
|
|
43
110
|
"opencode"
|
|
44
111
|
end
|
|
@@ -47,6 +114,14 @@ module AgentHarness
|
|
|
47
114
|
"OpenCode CLI"
|
|
48
115
|
end
|
|
49
116
|
|
|
117
|
+
def configuration_schema
|
|
118
|
+
{
|
|
119
|
+
fields: [],
|
|
120
|
+
auth_modes: [:api_key],
|
|
121
|
+
openai_compatible: true
|
|
122
|
+
}
|
|
123
|
+
end
|
|
124
|
+
|
|
50
125
|
def capabilities
|
|
51
126
|
{
|
|
52
127
|
streaming: false,
|
|
@@ -79,11 +154,26 @@ module AgentHarness
|
|
|
79
154
|
protected
|
|
80
155
|
|
|
81
156
|
def build_command(prompt, options)
|
|
82
|
-
cmd = [self.class.binary_name, "run"]
|
|
157
|
+
cmd = [self.class.installation_contract[:binary_name], "run"]
|
|
158
|
+
|
|
159
|
+
runtime = options[:provider_runtime]
|
|
160
|
+
if runtime
|
|
161
|
+
cmd += runtime.flags unless runtime.flags.empty?
|
|
162
|
+
end
|
|
163
|
+
|
|
83
164
|
cmd << prompt
|
|
84
165
|
cmd
|
|
85
166
|
end
|
|
86
167
|
|
|
168
|
+
def build_env(options)
|
|
169
|
+
env = super
|
|
170
|
+
runtime = options[:provider_runtime]
|
|
171
|
+
return env unless runtime
|
|
172
|
+
|
|
173
|
+
env["OPENAI_BASE_URL"] = runtime.base_url if runtime.base_url
|
|
174
|
+
env
|
|
175
|
+
end
|
|
176
|
+
|
|
87
177
|
def default_timeout
|
|
88
178
|
300
|
|
89
179
|
end
|
|
@@ -79,6 +79,60 @@ module AgentHarness
|
|
|
79
79
|
@providers.select { |_, klass| klass.available? }.keys
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
# Fetch installation metadata for a provider.
|
|
83
|
+
#
|
|
84
|
+
# @param name [Symbol, String] the provider name
|
|
85
|
+
# @param options [Hash] optional target selection (for example, `version:`)
|
|
86
|
+
# @return [Hash, nil] provider installation contract, or nil when the
|
|
87
|
+
# registered provider class does not define `.installation_contract`
|
|
88
|
+
# @raise [ConfigurationError] if provider not found
|
|
89
|
+
def installation_contract(name, **options)
|
|
90
|
+
provider_class = get(name)
|
|
91
|
+
return nil unless provider_class.respond_to?(:installation_contract)
|
|
92
|
+
|
|
93
|
+
provider_class.installation_contract(**options)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Get installation metadata for all providers that expose it.
|
|
97
|
+
#
|
|
98
|
+
# @return [Hash<Symbol, Hash>] installation contracts keyed by provider
|
|
99
|
+
def installation_contracts
|
|
100
|
+
ensure_builtin_providers_registered
|
|
101
|
+
|
|
102
|
+
@providers.each_with_object({}) do |(name, klass), contracts|
|
|
103
|
+
next unless klass.respond_to?(:installation_contract)
|
|
104
|
+
|
|
105
|
+
contract = klass.installation_contract
|
|
106
|
+
contracts[name] = contract if contract
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get smoke-test metadata for a provider.
|
|
111
|
+
#
|
|
112
|
+
# @param name [Symbol, String] the provider name
|
|
113
|
+
# @return [Hash, nil] smoke-test contract
|
|
114
|
+
# @raise [ConfigurationError] if the provider name is not registered
|
|
115
|
+
def smoke_test_contract(name)
|
|
116
|
+
klass = get(name)
|
|
117
|
+
return nil unless klass.respond_to?(:smoke_test_contract)
|
|
118
|
+
|
|
119
|
+
klass.smoke_test_contract
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Get smoke-test metadata for all providers that expose it.
|
|
123
|
+
#
|
|
124
|
+
# @return [Hash<Symbol, Hash>] smoke-test contracts keyed by provider
|
|
125
|
+
def smoke_test_contracts
|
|
126
|
+
ensure_builtin_providers_registered
|
|
127
|
+
|
|
128
|
+
@providers.each_with_object({}) do |(name, klass), contracts|
|
|
129
|
+
next unless klass.respond_to?(:smoke_test_contract)
|
|
130
|
+
|
|
131
|
+
contract = klass.smoke_test_contract
|
|
132
|
+
contracts[name] = contract if contract
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
82
136
|
# Reset registry (useful for testing)
|
|
83
137
|
#
|
|
84
138
|
# @return [void]
|
data/lib/agent_harness.rb
CHANGED
|
@@ -70,10 +70,11 @@ module AgentHarness
|
|
|
70
70
|
# Send a message using the orchestration layer
|
|
71
71
|
# @param prompt [String] the prompt to send
|
|
72
72
|
# @param provider [Symbol, nil] optional provider override
|
|
73
|
+
# @param executor [CommandExecutor, nil] per-request executor override
|
|
73
74
|
# @param options [Hash] additional options
|
|
74
75
|
# @return [Response] the response from the provider
|
|
75
|
-
def send_message(prompt, provider: nil, **options)
|
|
76
|
-
conductor.send_message(prompt, provider: provider, **options)
|
|
76
|
+
def send_message(prompt, provider: nil, executor: nil, **options)
|
|
77
|
+
conductor.send_message(prompt, provider: provider, executor: executor, **options)
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
# Get a provider instance
|
|
@@ -83,6 +84,64 @@ module AgentHarness
|
|
|
83
84
|
conductor.provider_manager.get_provider(name)
|
|
84
85
|
end
|
|
85
86
|
|
|
87
|
+
# Returns install metadata for a provider CLI when the provider exposes it.
|
|
88
|
+
#
|
|
89
|
+
# @param provider_name [Symbol, String] the provider name
|
|
90
|
+
# @param version [String, nil] optional explicit CLI version override
|
|
91
|
+
# @return [Hash, nil] installation metadata
|
|
92
|
+
def provider_install_contract(provider_name, version: nil)
|
|
93
|
+
provider_installation_contract(provider_name, **(version ? {version: version} : {}))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Get the installation contract for a provider CLI.
|
|
97
|
+
#
|
|
98
|
+
# @param name [Symbol, String] the provider name
|
|
99
|
+
# @param options [Hash] optional target selection (for example, `version:`)
|
|
100
|
+
# @return [Hash, nil] provider installation contract for the requested target
|
|
101
|
+
# @raise [ConfigurationError] if provider not found
|
|
102
|
+
def provider_installation_contract(name, **options)
|
|
103
|
+
Providers::Registry.instance.installation_contract(name, **options)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Get installation metadata for a provider CLI.
|
|
107
|
+
# @param provider_name [Symbol, String] the provider name
|
|
108
|
+
# @param options [Hash] optional target selection (for example, `version:`)
|
|
109
|
+
# @return [Hash, nil] installation contract
|
|
110
|
+
# @raise [ConfigurationError] if the provider name is not registered
|
|
111
|
+
def installation_contract(provider_name, **options)
|
|
112
|
+
Providers::Registry.instance.installation_contract(provider_name, **options)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Get all provider installation contracts exposed by agent-harness.
|
|
116
|
+
# @return [Hash<Symbol, Hash>] installation contracts keyed by provider
|
|
117
|
+
def installation_contracts
|
|
118
|
+
Providers::Registry.instance.installation_contracts
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Get smoke-test metadata for a provider CLI when the provider exposes it.
|
|
122
|
+
#
|
|
123
|
+
# @param provider_name [Symbol, String] the provider name
|
|
124
|
+
# @return [Hash, nil] smoke-test contract
|
|
125
|
+
def provider_smoke_test_contract(provider_name)
|
|
126
|
+
smoke_test_contract(provider_name)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Get smoke-test metadata for a provider CLI.
|
|
130
|
+
# @param provider_name [Symbol, String] the provider name
|
|
131
|
+
# @return [Hash, nil] smoke-test contract
|
|
132
|
+
# @raise [ConfigurationError] if the provider name is not registered
|
|
133
|
+
def smoke_test_contract(provider_name)
|
|
134
|
+
# Explicitly raise if provider is not registered to match documentation
|
|
135
|
+
raise ConfigurationError, "Unknown provider: #{provider_name}" unless Providers::Registry.instance.registered?(provider_name)
|
|
136
|
+
Providers::Registry.instance.smoke_test_contract(provider_name)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Get all provider smoke-test contracts exposed by agent-harness.
|
|
140
|
+
# @return [Hash<Symbol, Hash>] smoke-test contracts keyed by provider
|
|
141
|
+
def smoke_test_contracts
|
|
142
|
+
Providers::Registry.instance.smoke_test_contracts
|
|
143
|
+
end
|
|
144
|
+
|
|
86
145
|
# Check if authentication is valid for a provider
|
|
87
146
|
# @param provider_name [Symbol] the provider name
|
|
88
147
|
# @return [Boolean] true if auth is valid
|
|
@@ -120,17 +179,29 @@ module AgentHarness
|
|
|
120
179
|
# authentication, provider health status, and config validation checks.
|
|
121
180
|
#
|
|
122
181
|
# @param timeout [Integer] timeout in seconds for each check (defaults to configured value)
|
|
182
|
+
# @raise [ArgumentError] if provider_runtime is supplied; runtime overrides are
|
|
183
|
+
# only supported by `check_provider` to avoid leaking one provider's execution
|
|
184
|
+
# context into every other health check
|
|
123
185
|
# @return [Array<Hash>] health status for each provider
|
|
124
|
-
def check_providers(timeout: nil)
|
|
125
|
-
|
|
186
|
+
def check_providers(timeout: nil, executor: nil, provider_runtime: nil)
|
|
187
|
+
raise ArgumentError, "provider_runtime is only supported for single-provider health checks" unless provider_runtime.nil?
|
|
188
|
+
|
|
189
|
+
options = {}
|
|
190
|
+
options[:timeout] = timeout unless timeout.nil?
|
|
191
|
+
options[:executor] = executor unless executor.nil?
|
|
192
|
+
ProviderHealthCheck.check_all(**options)
|
|
126
193
|
end
|
|
127
194
|
|
|
128
195
|
# Check health of a single provider
|
|
129
196
|
# @param provider_name [Symbol] the provider name
|
|
130
197
|
# @param timeout [Integer, nil] timeout in seconds (nil lets ProviderHealthCheck apply its validated default)
|
|
131
198
|
# @return [Hash] health status with :name, :status, :message, :latency_ms
|
|
132
|
-
def check_provider(provider_name, timeout: nil)
|
|
133
|
-
|
|
199
|
+
def check_provider(provider_name, timeout: nil, executor: nil, provider_runtime: nil)
|
|
200
|
+
options = {}
|
|
201
|
+
options[:timeout] = timeout unless timeout.nil?
|
|
202
|
+
options[:executor] = executor unless executor.nil?
|
|
203
|
+
options[:provider_runtime] = provider_runtime unless provider_runtime.nil?
|
|
204
|
+
ProviderHealthCheck.check(provider_name, **options)
|
|
134
205
|
end
|
|
135
206
|
end
|
|
136
207
|
end
|
|
@@ -138,6 +209,7 @@ end
|
|
|
138
209
|
# Core components
|
|
139
210
|
require_relative "agent_harness/errors"
|
|
140
211
|
require_relative "agent_harness/mcp_server"
|
|
212
|
+
require_relative "agent_harness/provider_runtime"
|
|
141
213
|
require_relative "agent_harness/configuration"
|
|
142
214
|
require_relative "agent_harness/command_executor"
|
|
143
215
|
require_relative "agent_harness/docker_command_executor"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: agent-harness
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bart Agapinan
|
|
@@ -9,6 +9,26 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: logger
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.6'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '1.6'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '2.0'
|
|
12
32
|
- !ruby/object:Gem::Dependency
|
|
13
33
|
name: rake
|
|
14
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -92,6 +112,7 @@ files:
|
|
|
92
112
|
- lib/agent_harness/orchestration/provider_manager.rb
|
|
93
113
|
- lib/agent_harness/orchestration/rate_limiter.rb
|
|
94
114
|
- lib/agent_harness/provider_health_check.rb
|
|
115
|
+
- lib/agent_harness/provider_runtime.rb
|
|
95
116
|
- lib/agent_harness/providers/adapter.rb
|
|
96
117
|
- lib/agent_harness/providers/aider.rb
|
|
97
118
|
- lib/agent_harness/providers/anthropic.rb
|