agent-harness 0.5.7 → 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 +10 -0
- data/README.md +140 -2
- data/lib/agent_harness/authentication.rb +28 -9
- data/lib/agent_harness/orchestration/provider_manager.rb +26 -6
- data/lib/agent_harness/provider_health_check.rb +28 -6
- data/lib/agent_harness/providers/adapter.rb +589 -8
- data/lib/agent_harness/providers/aider.rb +55 -0
- data/lib/agent_harness/providers/anthropic.rb +94 -0
- data/lib/agent_harness/providers/codex.rb +19 -4
- data/lib/agent_harness/providers/cursor.rb +73 -1
- data/lib/agent_harness/providers/gemini.rb +9 -0
- data/lib/agent_harness/providers/github_copilot.rb +12 -0
- data/lib/agent_harness/providers/mistral_vibe.rb +9 -0
- data/lib/agent_harness/providers/opencode.rb +9 -0
- data/lib/agent_harness/providers/registry.rb +392 -18
- data/lib/agent_harness/version.rb +1 -1
- data/lib/agent_harness.rb +28 -0
- metadata +1 -1
|
@@ -6,6 +6,17 @@ module AgentHarness
|
|
|
6
6
|
#
|
|
7
7
|
# Provides integration with the Aider CLI tool.
|
|
8
8
|
class Aider < Base
|
|
9
|
+
UV_VERSION = "0.8.17"
|
|
10
|
+
SUPPORTED_CLI_VERSION = "0.86.2"
|
|
11
|
+
SUPPORTED_CLI_REQUIREMENT = Gem::Requirement.new(">= #{SUPPORTED_CLI_VERSION}", "< 0.87.0").freeze
|
|
12
|
+
PYTHON_VERSION = "python3.12"
|
|
13
|
+
BINARY_PATH = "/usr/local/bin/aider"
|
|
14
|
+
UV_TOOL_ENV = {
|
|
15
|
+
"UV_TOOL_BIN_DIR" => "/usr/local/bin",
|
|
16
|
+
"UV_TOOL_DIR" => "/opt/uv/tools",
|
|
17
|
+
"UV_PYTHON_INSTALL_DIR" => "/opt/uv/python"
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
9
20
|
class << self
|
|
10
21
|
def provider_name
|
|
11
22
|
:aider
|
|
@@ -50,6 +61,50 @@ module AgentHarness
|
|
|
50
61
|
]
|
|
51
62
|
end
|
|
52
63
|
|
|
64
|
+
def installation_contract(version: SUPPORTED_CLI_VERSION)
|
|
65
|
+
unless SUPPORTED_CLI_REQUIREMENT.satisfied_by?(Gem::Version.new(version))
|
|
66
|
+
raise ArgumentError,
|
|
67
|
+
"Unsupported Aider CLI version #{version.inspect}; " \
|
|
68
|
+
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
default_package = "aider-chat==#{version}".freeze
|
|
72
|
+
bootstrap_command = [
|
|
73
|
+
"python3", "-m", "pip", "install", "--no-cache-dir", "--break-system-packages", "uv==#{UV_VERSION}"
|
|
74
|
+
].freeze
|
|
75
|
+
install_command_prefix = [
|
|
76
|
+
"uv", "tool", "install", "--force", "--python", PYTHON_VERSION, "--with", "pip"
|
|
77
|
+
].freeze
|
|
78
|
+
install_command = (install_command_prefix + [default_package]).freeze
|
|
79
|
+
supported_versions = [version].freeze
|
|
80
|
+
version_requirement = SUPPORTED_CLI_REQUIREMENT.requirements
|
|
81
|
+
.map { |op, ver| "#{op} #{ver}".freeze }
|
|
82
|
+
.freeze
|
|
83
|
+
|
|
84
|
+
contract = {
|
|
85
|
+
source: :uv_tool,
|
|
86
|
+
bootstrap_source: :pip,
|
|
87
|
+
bootstrap_package: "uv==#{UV_VERSION}",
|
|
88
|
+
bootstrap_commands: [bootstrap_command].freeze,
|
|
89
|
+
install_environment: UV_TOOL_ENV,
|
|
90
|
+
package: default_package,
|
|
91
|
+
package_name: "aider-chat",
|
|
92
|
+
version: version,
|
|
93
|
+
version_format: "%{package_name}==%{version}",
|
|
94
|
+
version_requirement: version_requirement,
|
|
95
|
+
binary_name: binary_name,
|
|
96
|
+
binary_path: BINARY_PATH,
|
|
97
|
+
install_command_prefix: install_command_prefix,
|
|
98
|
+
install_command: install_command,
|
|
99
|
+
supported_versions: supported_versions
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
contract.each_value do |value|
|
|
103
|
+
value.freeze if value.is_a?(String)
|
|
104
|
+
end
|
|
105
|
+
contract.freeze
|
|
106
|
+
end
|
|
107
|
+
|
|
53
108
|
def smoke_test_contract
|
|
54
109
|
Base::DEFAULT_SMOKE_TEST_CONTRACT
|
|
55
110
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require "shellwords"
|
|
4
5
|
|
|
5
6
|
module AgentHarness
|
|
6
7
|
module Providers
|
|
@@ -15,6 +16,12 @@ module AgentHarness
|
|
|
15
16
|
class Anthropic < Base
|
|
16
17
|
# Model name pattern for Anthropic Claude models
|
|
17
18
|
MODEL_PATTERN = /^claude-[\d.-]+-(?:opus|sonnet|haiku)(?:-\d{8})?$/i
|
|
19
|
+
SUPPORTED_CLI_VERSION = "2.1.92"
|
|
20
|
+
SUPPORTED_CLI_REQUIREMENT = Gem::Requirement.new(">= #{SUPPORTED_CLI_VERSION}", "< 2.2.0").freeze
|
|
21
|
+
|
|
22
|
+
# Matches semver (e.g. "2.1.92"), optional pre-release (e.g. "2.1.92-beta.1"),
|
|
23
|
+
# or channel tokens (e.g. "latest", "stable").
|
|
24
|
+
VALID_VERSION_PATTERN = /\A(?:\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?|latest|stable)\z/
|
|
18
25
|
|
|
19
26
|
class << self
|
|
20
27
|
def provider_name
|
|
@@ -25,11 +32,79 @@ module AgentHarness
|
|
|
25
32
|
"claude"
|
|
26
33
|
end
|
|
27
34
|
|
|
35
|
+
def install_contract(version: nil)
|
|
36
|
+
target_version = version || SUPPORTED_CLI_VERSION
|
|
37
|
+
validate_version!(target_version)
|
|
38
|
+
version_requirement = SUPPORTED_CLI_REQUIREMENT.requirements
|
|
39
|
+
.map { |op, ver| "#{op} #{ver}" }
|
|
40
|
+
.join(", ")
|
|
41
|
+
channel_token = %w[latest stable].include?(target_version.to_s)
|
|
42
|
+
|
|
43
|
+
warning = "Review the downloaded installer before execution and verify any published checksum or signature metadata when available."
|
|
44
|
+
if channel_token
|
|
45
|
+
warning += " Channel '#{target_version}' is not pinned; the resolved version may fall " \
|
|
46
|
+
"outside the supported range (#{version_requirement}). Verify the installed version " \
|
|
47
|
+
"after installation."
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
provider: provider_name,
|
|
52
|
+
binary_name: binary_name,
|
|
53
|
+
binary_paths: [
|
|
54
|
+
"$HOME/.local/bin/claude",
|
|
55
|
+
binary_name
|
|
56
|
+
],
|
|
57
|
+
install: {
|
|
58
|
+
strategy: :shell,
|
|
59
|
+
source: "official",
|
|
60
|
+
command: "tmp_script=$(mktemp) && trap 'rm -f \"$tmp_script\"' EXIT && curl -fsSL https://claude.ai/install.sh -o \"$tmp_script\" && bash \"$tmp_script\" #{Shellwords.shellescape(target_version)}",
|
|
61
|
+
warning: warning,
|
|
62
|
+
post_install_binary_path: "$HOME/.local/bin/claude",
|
|
63
|
+
# When a channel token is used, include the requirement so
|
|
64
|
+
# consumers can validate the installed version post-install.
|
|
65
|
+
version_not_pinned: channel_token
|
|
66
|
+
},
|
|
67
|
+
supported_versions: {
|
|
68
|
+
default: SUPPORTED_CLI_VERSION,
|
|
69
|
+
requirement: version_requirement,
|
|
70
|
+
channel: "stable"
|
|
71
|
+
},
|
|
72
|
+
runtime_contract: {
|
|
73
|
+
available_via: binary_name,
|
|
74
|
+
build_command: [
|
|
75
|
+
binary_name,
|
|
76
|
+
"--print",
|
|
77
|
+
"--output-format=json"
|
|
78
|
+
],
|
|
79
|
+
required_features: [
|
|
80
|
+
"print_mode",
|
|
81
|
+
"json_output",
|
|
82
|
+
"mcp_config",
|
|
83
|
+
"mcp_list",
|
|
84
|
+
"dangerously_skip_permissions",
|
|
85
|
+
"models_list"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
|
|
28
91
|
def available?
|
|
29
92
|
executor = AgentHarness.configuration.command_executor
|
|
30
93
|
!!executor.which(binary_name)
|
|
31
94
|
end
|
|
32
95
|
|
|
96
|
+
def provider_metadata_overrides
|
|
97
|
+
{
|
|
98
|
+
auth: {
|
|
99
|
+
service: :anthropic,
|
|
100
|
+
api_family: :anthropic
|
|
101
|
+
},
|
|
102
|
+
identity: {
|
|
103
|
+
bot_usernames: %w[claude anthropic]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
33
108
|
def firewall_requirements
|
|
34
109
|
{
|
|
35
110
|
domains: [
|
|
@@ -87,6 +162,25 @@ module AgentHarness
|
|
|
87
162
|
|
|
88
163
|
private
|
|
89
164
|
|
|
165
|
+
def validate_version!(version)
|
|
166
|
+
version_str = version.to_s
|
|
167
|
+
|
|
168
|
+
unless VALID_VERSION_PATTERN.match?(version_str)
|
|
169
|
+
raise ArgumentError, "Invalid version: #{version.inspect}. " \
|
|
170
|
+
"Must be a semver string (e.g. '2.1.92'), optional pre-release suffix, or a channel token ('latest', 'stable')."
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Channel tokens are not concrete versions; skip requirement check.
|
|
174
|
+
return if %w[latest stable].include?(version_str)
|
|
175
|
+
|
|
176
|
+
# Validate concrete versions against the supported range.
|
|
177
|
+
gem_version = Gem::Version.new(version_str)
|
|
178
|
+
return if SUPPORTED_CLI_REQUIREMENT.satisfied_by?(gem_version)
|
|
179
|
+
|
|
180
|
+
raise ArgumentError, "Version #{version.inspect} is outside the supported range " \
|
|
181
|
+
"(#{SUPPORTED_CLI_REQUIREMENT}). Update SUPPORTED_CLI_REQUIREMENT before targeting this version."
|
|
182
|
+
end
|
|
183
|
+
|
|
90
184
|
def parse_models_list(output)
|
|
91
185
|
return [] if output.nil? || output.empty?
|
|
92
186
|
|
|
@@ -25,6 +25,15 @@ module AgentHarness
|
|
|
25
25
|
!!executor.which(binary_name)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def provider_metadata_overrides
|
|
29
|
+
{
|
|
30
|
+
auth: {
|
|
31
|
+
service: :openai,
|
|
32
|
+
api_family: :openai
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
28
37
|
def firewall_requirements
|
|
29
38
|
{
|
|
30
39
|
domains: [
|
|
@@ -53,11 +62,17 @@ module AgentHarness
|
|
|
53
62
|
]
|
|
54
63
|
end
|
|
55
64
|
|
|
56
|
-
def installation_contract
|
|
57
|
-
|
|
65
|
+
def installation_contract(version: SUPPORTED_CLI_VERSION)
|
|
66
|
+
unless SUPPORTED_CLI_REQUIREMENT.satisfied_by?(Gem::Version.new(version))
|
|
67
|
+
raise ArgumentError,
|
|
68
|
+
"Unsupported Codex CLI version #{version.inspect}; " \
|
|
69
|
+
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
default_package = "@openai/codex@#{version}".freeze
|
|
58
73
|
install_command_prefix = ["npm", "install", "-g", "--ignore-scripts"].freeze
|
|
59
74
|
install_command = (install_command_prefix + [default_package]).freeze
|
|
60
|
-
supported_versions = [
|
|
75
|
+
supported_versions = [version].freeze
|
|
61
76
|
version_requirement = SUPPORTED_CLI_REQUIREMENT.requirements
|
|
62
77
|
.map { |op, ver| "#{op} #{ver}".freeze }
|
|
63
78
|
.freeze
|
|
@@ -66,7 +81,7 @@ module AgentHarness
|
|
|
66
81
|
source: :npm,
|
|
67
82
|
package: default_package,
|
|
68
83
|
package_name: "@openai/codex",
|
|
69
|
-
version:
|
|
84
|
+
version: version,
|
|
70
85
|
version_requirement: version_requirement,
|
|
71
86
|
binary_name: binary_name,
|
|
72
87
|
install_command_prefix: install_command_prefix,
|
|
@@ -12,6 +12,12 @@ module AgentHarness
|
|
|
12
12
|
# provider = AgentHarness::Providers::Cursor.new
|
|
13
13
|
# response = provider.send_message(prompt: "Hello!")
|
|
14
14
|
class Cursor < Base
|
|
15
|
+
INSTALL_SCRIPT_URL = "https://cursor.com/install"
|
|
16
|
+
INSTALL_TARGET_LATEST = "latest"
|
|
17
|
+
INSTALL_BUILD = "2026.03.30-a5d3e17"
|
|
18
|
+
INSTALL_SCRIPT_SHA256 = "8371988b483abec13c07c10e95cccc839da81ebf9596e430d3c90835a227cbad"
|
|
19
|
+
INSTALL_LINUX_X64_PACKAGE_SHA256 = "e0d4b611db111d2dbe76474386271bff3e1dbb2cc6ddf527f9d5d5801b2ce2a0"
|
|
20
|
+
|
|
15
21
|
class << self
|
|
16
22
|
def provider_name
|
|
17
23
|
:cursor
|
|
@@ -26,6 +32,15 @@ module AgentHarness
|
|
|
26
32
|
!!executor.which(binary_name)
|
|
27
33
|
end
|
|
28
34
|
|
|
35
|
+
def provider_metadata_overrides
|
|
36
|
+
{
|
|
37
|
+
auth: {
|
|
38
|
+
service: :cursor,
|
|
39
|
+
api_family: :cursor
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
29
44
|
def firewall_requirements
|
|
30
45
|
{
|
|
31
46
|
domains: [
|
|
@@ -84,9 +99,66 @@ module AgentHarness
|
|
|
84
99
|
family_name.match?(/^(claude|gpt|cursor)-/)
|
|
85
100
|
end
|
|
86
101
|
|
|
102
|
+
def install_metadata(version: nil)
|
|
103
|
+
install_target = normalize_install_target(version)
|
|
104
|
+
linux_x64_package_url = package_url_for(os: "linux", arch: "x64")
|
|
105
|
+
|
|
106
|
+
{
|
|
107
|
+
source: {
|
|
108
|
+
type: :shell_script,
|
|
109
|
+
url: INSTALL_SCRIPT_URL,
|
|
110
|
+
resolved_version: INSTALL_BUILD,
|
|
111
|
+
default_artifact_url: linux_x64_package_url
|
|
112
|
+
},
|
|
113
|
+
checksum: {
|
|
114
|
+
strategy: :sha256,
|
|
115
|
+
targets: {
|
|
116
|
+
script: {
|
|
117
|
+
url: INSTALL_SCRIPT_URL,
|
|
118
|
+
value: INSTALL_SCRIPT_SHA256
|
|
119
|
+
},
|
|
120
|
+
artifacts: {
|
|
121
|
+
"linux/x64" => {
|
|
122
|
+
url: linux_x64_package_url,
|
|
123
|
+
value: INSTALL_LINUX_X64_PACKAGE_SHA256
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
binary: {
|
|
129
|
+
name: binary_name,
|
|
130
|
+
path: "$HOME/.local/bin/#{binary_name}",
|
|
131
|
+
suggested_global_path: "/usr/local/bin/#{binary_name}"
|
|
132
|
+
},
|
|
133
|
+
version: {
|
|
134
|
+
default: INSTALL_TARGET_LATEST,
|
|
135
|
+
supported: install_target,
|
|
136
|
+
command: [binary_name, "--version"]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
end
|
|
140
|
+
|
|
87
141
|
def smoke_test_contract
|
|
88
142
|
Base::DEFAULT_SMOKE_TEST_CONTRACT
|
|
89
143
|
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
def package_url_for(os:, arch:)
|
|
148
|
+
format(
|
|
149
|
+
"https://downloads.cursor.com/lab/%<build>s/%<os>s/%<arch>s/agent-cli-package.tar.gz",
|
|
150
|
+
build: INSTALL_BUILD,
|
|
151
|
+
os: os,
|
|
152
|
+
arch: arch
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def normalize_install_target(version)
|
|
157
|
+
target = version.nil? ? INSTALL_TARGET_LATEST : version.to_s
|
|
158
|
+
return target if target == INSTALL_TARGET_LATEST
|
|
159
|
+
|
|
160
|
+
raise ArgumentError, "Unsupported Cursor install target: #{version.inspect}"
|
|
161
|
+
end
|
|
90
162
|
end
|
|
91
163
|
|
|
92
164
|
def name
|
|
@@ -253,7 +325,7 @@ module AgentHarness
|
|
|
253
325
|
return nil unless self.class.available?
|
|
254
326
|
|
|
255
327
|
begin
|
|
256
|
-
result = @executor.execute([
|
|
328
|
+
result = @executor.execute([self.class.binary_name, "mcp", "list"], timeout: 5)
|
|
257
329
|
return nil unless result.success?
|
|
258
330
|
|
|
259
331
|
parse_mcp_servers_output(result.stdout)
|
|
@@ -30,6 +30,15 @@ module AgentHarness
|
|
|
30
30
|
!!executor.which(binary_name)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def provider_metadata_overrides
|
|
34
|
+
{
|
|
35
|
+
auth: {
|
|
36
|
+
service: :google,
|
|
37
|
+
api_family: :gemini
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
33
42
|
def install_contract(version: SUPPORTED_CLI_VERSION)
|
|
34
43
|
parsed_version = begin
|
|
35
44
|
Gem::Version.new(version)
|
|
@@ -37,6 +37,18 @@ module AgentHarness
|
|
|
37
37
|
!!executor.which(binary_name)
|
|
38
38
|
end
|
|
39
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
|
+
|
|
40
52
|
def firewall_requirements
|
|
41
53
|
{
|
|
42
54
|
domains: [
|