agent-harness 0.5.6 → 0.5.8
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 +26 -0
- data/README.md +216 -3
- data/lib/agent_harness/authentication.rb +28 -9
- 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 +46 -18
- data/lib/agent_harness/provider_health_check.rb +243 -63
- data/lib/agent_harness/provider_runtime.rb +20 -3
- data/lib/agent_harness/providers/adapter.rb +717 -0
- data/lib/agent_harness/providers/aider.rb +59 -0
- data/lib/agent_harness/providers/anthropic.rb +98 -0
- data/lib/agent_harness/providers/base.rb +46 -10
- data/lib/agent_harness/providers/codex.rb +68 -9
- data/lib/agent_harness/providers/cursor.rb +90 -2
- data/lib/agent_harness/providers/gemini.rb +43 -0
- data/lib/agent_harness/providers/github_copilot.rb +38 -6
- data/lib/agent_harness/providers/kilocode.rb +39 -0
- data/lib/agent_harness/providers/mistral_vibe.rb +13 -0
- data/lib/agent_harness/providers/opencode.rb +77 -1
- data/lib/agent_harness/providers/registry.rb +446 -18
- data/lib/agent_harness/version.rb +1 -1
- data/lib/agent_harness.rb +105 -6
- metadata +21 -1
|
@@ -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?
|
|
@@ -23,6 +37,18 @@ module AgentHarness
|
|
|
23
37
|
!!executor.which(binary_name)
|
|
24
38
|
end
|
|
25
39
|
|
|
40
|
+
def provider_metadata_overrides
|
|
41
|
+
{
|
|
42
|
+
auth: {
|
|
43
|
+
service: :github,
|
|
44
|
+
api_family: :github_copilot
|
|
45
|
+
},
|
|
46
|
+
identity: {
|
|
47
|
+
bot_usernames: ["github-copilot[bot]"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
26
52
|
def firewall_requirements
|
|
27
53
|
{
|
|
28
54
|
domains: [
|
|
@@ -56,6 +82,10 @@ module AgentHarness
|
|
|
56
82
|
]
|
|
57
83
|
end
|
|
58
84
|
|
|
85
|
+
def smoke_test_contract
|
|
86
|
+
SMOKE_TEST_CONTRACT
|
|
87
|
+
end
|
|
88
|
+
|
|
59
89
|
def model_family(provider_model_name)
|
|
60
90
|
provider_model_name
|
|
61
91
|
end
|
|
@@ -116,10 +146,10 @@ module AgentHarness
|
|
|
116
146
|
|
|
117
147
|
def execution_semantics
|
|
118
148
|
{
|
|
119
|
-
prompt_delivery: :
|
|
149
|
+
prompt_delivery: :arg,
|
|
120
150
|
output_format: :text,
|
|
121
151
|
sandbox_aware: false,
|
|
122
|
-
uses_subcommand:
|
|
152
|
+
uses_subcommand: true,
|
|
123
153
|
non_interactive_flag: nil,
|
|
124
154
|
legitimate_exit_codes: [0],
|
|
125
155
|
stderr_is_diagnostic: true,
|
|
@@ -155,10 +185,12 @@ module AgentHarness
|
|
|
155
185
|
protected
|
|
156
186
|
|
|
157
187
|
def build_command(prompt, options)
|
|
158
|
-
cmd = [self.class.binary_name, "-
|
|
188
|
+
cmd = [self.class.binary_name, "what-the-shell", prompt]
|
|
159
189
|
|
|
160
|
-
#
|
|
161
|
-
|
|
190
|
+
# Opt in to unrestricted tool access explicitly to preserve a safe default.
|
|
191
|
+
if supports_dangerous_mode? && options[:dangerous_mode]
|
|
192
|
+
cmd += dangerous_mode_flags
|
|
193
|
+
end
|
|
162
194
|
|
|
163
195
|
# Add session support if provided
|
|
164
196
|
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
|
|
@@ -20,6 +20,15 @@ module AgentHarness
|
|
|
20
20
|
!!executor.which(binary_name)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
def provider_metadata_overrides
|
|
24
|
+
{
|
|
25
|
+
auth: {
|
|
26
|
+
service: :mistral,
|
|
27
|
+
api_family: :mistral
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
23
32
|
def firewall_requirements
|
|
24
33
|
{
|
|
25
34
|
domains: [
|
|
@@ -37,6 +46,10 @@ module AgentHarness
|
|
|
37
46
|
return [] unless available?
|
|
38
47
|
[]
|
|
39
48
|
end
|
|
49
|
+
|
|
50
|
+
def smoke_test_contract
|
|
51
|
+
Base::DEFAULT_SMOKE_TEST_CONTRACT
|
|
52
|
+
end
|
|
40
53
|
end
|
|
41
54
|
|
|
42
55
|
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
|
|
@@ -20,6 +29,15 @@ module AgentHarness
|
|
|
20
29
|
!!executor.which(binary_name)
|
|
21
30
|
end
|
|
22
31
|
|
|
32
|
+
def provider_metadata_overrides
|
|
33
|
+
{
|
|
34
|
+
auth: {
|
|
35
|
+
service: :openai,
|
|
36
|
+
api_family: :openai_compatible
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
23
41
|
def firewall_requirements
|
|
24
42
|
{
|
|
25
43
|
domains: [
|
|
@@ -37,8 +55,66 @@ module AgentHarness
|
|
|
37
55
|
return [] unless available?
|
|
38
56
|
[]
|
|
39
57
|
end
|
|
58
|
+
|
|
59
|
+
def installation_contract(version: SUPPORTED_CLI_VERSION)
|
|
60
|
+
normalized_version = normalize_install_version(version)
|
|
61
|
+
return DEFAULT_INSTALLATION_CONTRACT if normalized_version == SUPPORTED_CLI_VERSION
|
|
62
|
+
|
|
63
|
+
build_installation_contract(normalized_version)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def install_command(version: SUPPORTED_CLI_VERSION)
|
|
67
|
+
installation_contract(version: version)[:install_command]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def smoke_test_contract
|
|
71
|
+
Base::DEFAULT_SMOKE_TEST_CONTRACT
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def build_installation_contract(version)
|
|
77
|
+
package = "#{CLI_PACKAGE}@#{version}".freeze
|
|
78
|
+
install_command = (INSTALL_COMMAND_PREFIX + [package]).freeze
|
|
79
|
+
|
|
80
|
+
contract = {
|
|
81
|
+
source: :npm,
|
|
82
|
+
package: package,
|
|
83
|
+
package_name: CLI_PACKAGE,
|
|
84
|
+
version: version,
|
|
85
|
+
version_requirement: VERSION_REQUIREMENT_STRINGS,
|
|
86
|
+
binary_name: binary_name,
|
|
87
|
+
install_command_prefix: INSTALL_COMMAND_PREFIX,
|
|
88
|
+
install_command: install_command,
|
|
89
|
+
supported_versions: SUPPORTED_CLI_VERSIONS
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
contract.each_value do |value|
|
|
93
|
+
value.freeze if value.is_a?(String)
|
|
94
|
+
end
|
|
95
|
+
contract.freeze
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def normalize_install_version(version)
|
|
99
|
+
raise ArgumentError, unsupported_version_message(version) unless version.is_a?(String) && !version.strip.empty?
|
|
100
|
+
|
|
101
|
+
normalized_version = version.strip
|
|
102
|
+
parsed_version = Gem::Version.new(normalized_version)
|
|
103
|
+
return normalized_version if SUPPORTED_CLI_REQUIREMENT.satisfied_by?(parsed_version)
|
|
104
|
+
|
|
105
|
+
raise ArgumentError, unsupported_version_message(version)
|
|
106
|
+
rescue ArgumentError
|
|
107
|
+
raise ArgumentError, unsupported_version_message(version)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def unsupported_version_message(version)
|
|
111
|
+
"Unsupported OpenCode CLI version #{version.inspect}; " \
|
|
112
|
+
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
|
|
113
|
+
end
|
|
40
114
|
end
|
|
41
115
|
|
|
116
|
+
DEFAULT_INSTALLATION_CONTRACT = build_installation_contract(SUPPORTED_CLI_VERSION)
|
|
117
|
+
|
|
42
118
|
def name
|
|
43
119
|
"opencode"
|
|
44
120
|
end
|
|
@@ -87,7 +163,7 @@ module AgentHarness
|
|
|
87
163
|
protected
|
|
88
164
|
|
|
89
165
|
def build_command(prompt, options)
|
|
90
|
-
cmd = [self.class.binary_name, "run"]
|
|
166
|
+
cmd = [self.class.installation_contract[:binary_name], "run"]
|
|
91
167
|
|
|
92
168
|
runtime = options[:provider_runtime]
|
|
93
169
|
if runtime
|