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.
@@ -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: :flag,
149
+ prompt_delivery: :arg,
120
150
  output_format: :text,
121
151
  sandbox_aware: false,
122
- uses_subcommand: false,
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, "-p", prompt]
188
+ cmd = [self.class.binary_name, "what-the-shell", prompt]
159
189
 
160
- # Add dangerous mode flags by default for automation
161
- cmd += dangerous_mode_flags if supports_dangerous_mode?
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