agent-harness 0.15.0 → 0.16.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 +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +14 -0
- data/lib/agent_harness/providers/adapter.rb +9 -0
- data/lib/agent_harness/providers/aider.rb +20 -0
- data/lib/agent_harness/providers/anthropic.rb +11 -0
- data/lib/agent_harness/providers/base.rb +50 -0
- data/lib/agent_harness/providers/cursor.rb +22 -0
- data/lib/agent_harness/providers/github_copilot.rb +49 -7
- data/lib/agent_harness/providers/kilocode.rb +10 -4
- data/lib/agent_harness/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c7a73c78312193fa3d7a73f3fa32aa032f9a5c8748dd0a679b2f05abc0853fe
|
|
4
|
+
data.tar.gz: ca7ed61365d3df1aaa8c3b48ee0100804e7ff49ad08f4da73cc07ce444934b79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7bbd40a153f7565617f151d2dea5d7bdded0314b3d144971947ab92699dde8c0851a83ab15eed45f0a4eb34faf30a75274de88434963420b28a68263db1c77fd
|
|
7
|
+
data.tar.gz: c97a38b9bdb74397b18a9c37f641e3469be3e0fa9532a8728e2f5a8517b2c14bacd5b4b65441fda0c28f2397ab21f83ead4ceaa0aa17585c02c32b5a22d5d8f8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.16.1](https://github.com/viamin/agent-harness/compare/agent-harness/v0.16.0...agent-harness/v0.16.1) (2026-05-03)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **kilocode:** config_file_content generates invalid JSON for kilo CLI v7.1.3 ([#190](https://github.com/viamin/agent-harness/issues/190)) ([0f7e24a](https://github.com/viamin/agent-harness/commit/0f7e24a8c342045d16a9332385e93f0607d0845e))
|
|
9
|
+
|
|
10
|
+
## [0.16.0](https://github.com/viamin/agent-harness/compare/agent-harness/v0.15.0...agent-harness/v0.16.0) (2026-05-03)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* Add plan-only / dry-run API returning command+env without execution ([#192](https://github.com/viamin/agent-harness/issues/192)) ([0e6a105](https://github.com/viamin/agent-harness/commit/0e6a1053515e0495900b67c5845a2f95c571f055))
|
|
16
|
+
|
|
3
17
|
## [0.15.0](https://github.com/viamin/agent-harness/compare/agent-harness/v0.14.1...agent-harness/v0.15.0) (2026-05-03)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -775,6 +775,15 @@ module AgentHarness
|
|
|
775
775
|
raise NotImplementedError, "#{self.class} must implement #send_message"
|
|
776
776
|
end
|
|
777
777
|
|
|
778
|
+
# Return the provider CLI execution plan without executing the command.
|
|
779
|
+
#
|
|
780
|
+
# @param prompt [String] the prompt to send
|
|
781
|
+
# @param options [Hash] provider-specific options
|
|
782
|
+
# @return [Hash] with :command, :env, and :preparation keys
|
|
783
|
+
def plan_execution(prompt:, **options)
|
|
784
|
+
raise NotImplementedError, "#{self.class} must implement #plan_execution"
|
|
785
|
+
end
|
|
786
|
+
|
|
778
787
|
# Provider configuration schema for app-driven setup UIs
|
|
779
788
|
#
|
|
780
789
|
# Returns metadata describing the configurable fields, supported
|
|
@@ -263,6 +263,26 @@ module AgentHarness
|
|
|
263
263
|
cleanup_llm_history_file!(llm_history_path)
|
|
264
264
|
end
|
|
265
265
|
|
|
266
|
+
def plan_execution(prompt:, **options)
|
|
267
|
+
log_debug("plan_execution_start", prompt_length: prompt.length, options: options.keys)
|
|
268
|
+
|
|
269
|
+
options = normalize_provider_runtime(options)
|
|
270
|
+
options = normalize_mcp_servers(options)
|
|
271
|
+
validate_mcp_servers!(options[:mcp_servers]) if options[:mcp_servers]&.any?
|
|
272
|
+
|
|
273
|
+
llm_history_path = generate_llm_history_path
|
|
274
|
+
|
|
275
|
+
{
|
|
276
|
+
command: build_command(prompt, options.merge(llm_history_path: llm_history_path)),
|
|
277
|
+
env: build_env(options),
|
|
278
|
+
preparation: build_execution_preparation(options)
|
|
279
|
+
}
|
|
280
|
+
rescue McpConfigurationError, McpUnsupportedError, McpTransportUnsupportedError
|
|
281
|
+
raise
|
|
282
|
+
rescue => e
|
|
283
|
+
handle_error(e, prompt: prompt, options: options)
|
|
284
|
+
end
|
|
285
|
+
|
|
266
286
|
# Parse raw container output into a Response.
|
|
267
287
|
#
|
|
268
288
|
# Overrides the base implementation to support the
|
|
@@ -386,6 +386,17 @@ module AgentHarness
|
|
|
386
386
|
cleanup_mcp_tempfiles!
|
|
387
387
|
end
|
|
388
388
|
|
|
389
|
+
def plan_execution(prompt:, **options)
|
|
390
|
+
if options[:mode] == :text
|
|
391
|
+
raise ProviderError,
|
|
392
|
+
"Anthropic text mode uses the HTTP transport and does not produce a CLI execution plan"
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
super
|
|
396
|
+
ensure
|
|
397
|
+
cleanup_mcp_tempfiles!
|
|
398
|
+
end
|
|
399
|
+
|
|
389
400
|
def api_key_env_var_names = ["ANTHROPIC_API_KEY"]
|
|
390
401
|
|
|
391
402
|
def api_key_unset_vars = ["ANTHROPIC_BASE_URL", "ANTHROPIC_HEADER_X_AGENT_RUN_ID", "ANTHROPIC_HEADER_X_PROXY_TOKEN"]
|
|
@@ -197,6 +197,56 @@ module AgentHarness
|
|
|
197
197
|
handle_error(e, prompt: prompt, options: options)
|
|
198
198
|
end
|
|
199
199
|
|
|
200
|
+
# Return the provider CLI execution plan without executing it.
|
|
201
|
+
#
|
|
202
|
+
# @param prompt [String] the prompt to send
|
|
203
|
+
# @param options [Hash] additional options
|
|
204
|
+
# @return [Hash] with :command, :env, and :preparation keys
|
|
205
|
+
def plan_execution(prompt:, **options)
|
|
206
|
+
log_debug("plan_execution_start", prompt_length: prompt.length, options: options.keys)
|
|
207
|
+
|
|
208
|
+
if options[:mode] == :text && !supports_text_mode?
|
|
209
|
+
log_debug("text_mode_cli_fallback", provider: self.class.provider_name)
|
|
210
|
+
options = options.except(:mode).merge(tools: :none)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
if options[:tools] && !supports_tool_control?
|
|
214
|
+
log_debug("tools_option_unsupported",
|
|
215
|
+
provider: self.class.provider_name,
|
|
216
|
+
tools: options[:tools])
|
|
217
|
+
@logger&.warn(
|
|
218
|
+
"[AgentHarness::#{self.class.provider_name}] tools option is not supported " \
|
|
219
|
+
"by this provider and will be ignored"
|
|
220
|
+
)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
options = normalize_provider_runtime(options)
|
|
224
|
+
|
|
225
|
+
extension_context = apply_extensions_to_prompt(prompt, options)
|
|
226
|
+
prompt = extension_context.prompt
|
|
227
|
+
options = extension_context.options
|
|
228
|
+
options = normalize_sub_agent(options)
|
|
229
|
+
prompt = apply_sub_agent_to_prompt(prompt, options[:translated_sub_agent])
|
|
230
|
+
|
|
231
|
+
options = normalize_mcp_servers(options)
|
|
232
|
+
validate_mcp_servers!(options[:mcp_servers]) if options[:mcp_servers]&.any?
|
|
233
|
+
|
|
234
|
+
{
|
|
235
|
+
command: build_command(prompt, options),
|
|
236
|
+
env: build_env(options),
|
|
237
|
+
preparation: build_execution_preparation(options)
|
|
238
|
+
}
|
|
239
|
+
rescue ExtensionCompatibilityError, McpConfigurationError, McpUnsupportedError, McpTransportUnsupportedError
|
|
240
|
+
raise
|
|
241
|
+
rescue => e
|
|
242
|
+
handle_error(e, prompt: prompt, options: options)
|
|
243
|
+
ensure
|
|
244
|
+
# build_command may call build_mcp_flags which creates tempfiles (and
|
|
245
|
+
# in Docker even invokes the executor) via write_mcp_config_file.
|
|
246
|
+
# Clean up so that planning has no lasting side effects.
|
|
247
|
+
cleanup_mcp_tempfiles! if respond_to?(:cleanup_mcp_tempfiles!, true)
|
|
248
|
+
end
|
|
249
|
+
|
|
200
250
|
# Send a multi-turn chat message via the provider's chat transport.
|
|
201
251
|
#
|
|
202
252
|
# Providers that support chat mode can accept either +conversation:+
|
|
@@ -312,6 +312,28 @@ module AgentHarness
|
|
|
312
312
|
handle_error(e, prompt: prompt, options: options)
|
|
313
313
|
end
|
|
314
314
|
|
|
315
|
+
def plan_execution(prompt:, **options)
|
|
316
|
+
log_debug("plan_execution_start", prompt_length: prompt.length, options: options.keys)
|
|
317
|
+
|
|
318
|
+
options = normalize_provider_runtime(options)
|
|
319
|
+
options = normalize_mcp_servers(options)
|
|
320
|
+
validate_mcp_servers!(options[:mcp_servers]) if options[:mcp_servers]&.any?
|
|
321
|
+
|
|
322
|
+
runtime = options[:provider_runtime]
|
|
323
|
+
cmd = [self.class.binary_name, "-p"]
|
|
324
|
+
cmd.concat(runtime.flags) if runtime&.flags&.any?
|
|
325
|
+
|
|
326
|
+
{
|
|
327
|
+
command: cmd,
|
|
328
|
+
env: build_env(options),
|
|
329
|
+
preparation: build_execution_preparation(options)
|
|
330
|
+
}
|
|
331
|
+
rescue McpConfigurationError, McpUnsupportedError, McpTransportUnsupportedError
|
|
332
|
+
raise
|
|
333
|
+
rescue => e
|
|
334
|
+
handle_error(e, prompt: prompt, options: options)
|
|
335
|
+
end
|
|
336
|
+
|
|
315
337
|
protected
|
|
316
338
|
|
|
317
339
|
def build_command(prompt, options)
|
|
@@ -188,11 +188,11 @@ module AgentHarness
|
|
|
188
188
|
["--allow-all"]
|
|
189
189
|
end
|
|
190
190
|
|
|
191
|
-
def supports_sessions?(probe_timeout: nil, env: {}, version:
|
|
191
|
+
def supports_sessions?(probe_timeout: nil, env: {}, version: :not_provided)
|
|
192
192
|
legacy_prompt_cli?(version: version, probe_timeout: probe_timeout, env: env)
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
-
def session_flags(session_id, version:
|
|
195
|
+
def session_flags(session_id, version: :not_provided, probe_timeout: nil, env: {})
|
|
196
196
|
return [] unless session_id && !session_id.empty?
|
|
197
197
|
return [] unless legacy_prompt_cli?(version: version, probe_timeout: probe_timeout, env: env)
|
|
198
198
|
|
|
@@ -345,6 +345,30 @@ module AgentHarness
|
|
|
345
345
|
handle_error(e, prompt: prompt, options: options)
|
|
346
346
|
end
|
|
347
347
|
|
|
348
|
+
def plan_execution(prompt:, **options)
|
|
349
|
+
log_debug("plan_execution_start", prompt_length: prompt.length, options: options.keys)
|
|
350
|
+
|
|
351
|
+
options = normalize_provider_runtime(options)
|
|
352
|
+
options = normalize_mcp_servers(options)
|
|
353
|
+
validate_mcp_servers!(options[:mcp_servers]) if options[:mcp_servers]&.any?
|
|
354
|
+
|
|
355
|
+
env = build_env(options)
|
|
356
|
+
version = planned_copilot_cli_version(env)
|
|
357
|
+
raise unsupported_subcommand_cli_error if subcommand_cli_version?(version)
|
|
358
|
+
|
|
359
|
+
options = options.merge(_command_env: env, _planned_cli_version: version)
|
|
360
|
+
|
|
361
|
+
{
|
|
362
|
+
command: build_command(prompt, options),
|
|
363
|
+
env: env,
|
|
364
|
+
preparation: build_execution_preparation(options)
|
|
365
|
+
}
|
|
366
|
+
rescue McpConfigurationError, McpUnsupportedError, McpTransportUnsupportedError
|
|
367
|
+
raise
|
|
368
|
+
rescue => e
|
|
369
|
+
handle_error(e, prompt: prompt, options: options)
|
|
370
|
+
end
|
|
371
|
+
|
|
348
372
|
# Parse raw container output into a Response.
|
|
349
373
|
#
|
|
350
374
|
# Overrides the base implementation to support the
|
|
@@ -377,7 +401,14 @@ module AgentHarness
|
|
|
377
401
|
def build_command(prompt, options)
|
|
378
402
|
env = options.fetch(:_command_env) { build_env(options) }
|
|
379
403
|
runtime = options[:provider_runtime]
|
|
380
|
-
version =
|
|
404
|
+
version = if options.key?(:_planned_cli_version)
|
|
405
|
+
options[:_planned_cli_version]
|
|
406
|
+
else
|
|
407
|
+
copilot_cli_version(
|
|
408
|
+
probe_timeout: options[:_version_probe_timeout],
|
|
409
|
+
env: env
|
|
410
|
+
)
|
|
411
|
+
end
|
|
381
412
|
|
|
382
413
|
raise unsupported_subcommand_cli_error if subcommand_cli_version?(version)
|
|
383
414
|
|
|
@@ -440,13 +471,13 @@ module AgentHarness
|
|
|
440
471
|
["--allow-all-tools"]
|
|
441
472
|
end
|
|
442
473
|
|
|
443
|
-
def supports_json_output_format?(probe_timeout: nil, env: {}, version:
|
|
444
|
-
version
|
|
474
|
+
def supports_json_output_format?(probe_timeout: nil, env: {}, version: :not_provided)
|
|
475
|
+
version = copilot_cli_version(probe_timeout: probe_timeout, env: env) if version == :not_provided
|
|
445
476
|
!version.nil? && !subcommand_cli_version?(version) && version >= JSON_OUTPUT_MIN_VERSION
|
|
446
477
|
end
|
|
447
478
|
|
|
448
|
-
def legacy_prompt_cli?(probe_timeout: nil, env: {}, version:
|
|
449
|
-
version
|
|
479
|
+
def legacy_prompt_cli?(probe_timeout: nil, env: {}, version: :not_provided)
|
|
480
|
+
version = copilot_cli_version(probe_timeout: probe_timeout, env: env) if version == :not_provided
|
|
450
481
|
!version.nil? && !subcommand_cli_version?(version)
|
|
451
482
|
end
|
|
452
483
|
|
|
@@ -475,6 +506,17 @@ module AgentHarness
|
|
|
475
506
|
@copilot_cli_versions[cache_key] = nil if defined?(cache_key)
|
|
476
507
|
end
|
|
477
508
|
|
|
509
|
+
def planned_copilot_cli_version(env)
|
|
510
|
+
cache_key = version_probe_cache_key(env)
|
|
511
|
+
@copilot_cli_versions ||= {}
|
|
512
|
+
return @copilot_cli_versions[cache_key] if @copilot_cli_versions.key?(cache_key)
|
|
513
|
+
|
|
514
|
+
# When no cached version is available (cold start), return nil so
|
|
515
|
+
# build_command falls back to the conservative -s flag path, matching
|
|
516
|
+
# the behavior of send_message when the version probe returns nil.
|
|
517
|
+
nil
|
|
518
|
+
end
|
|
519
|
+
|
|
478
520
|
def version_probe_cache_key(env)
|
|
479
521
|
[
|
|
480
522
|
probe_env_cache_component(env, "PATH", inherited_label: :inherited_path, override_label: :path_override),
|
|
@@ -130,10 +130,16 @@ module AgentHarness
|
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
def config_file_content(options = {})
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
# Only use explicit provider_name or default to "openai".
|
|
134
|
+
# api_provider is a generic backend label (e.g. "openrouter") that is not
|
|
135
|
+
# a valid Kilo built-in provider ID, so we must not fall back to it here.
|
|
136
|
+
provider_name = options[:provider_name] || "openai"
|
|
137
|
+
model_id = options[:model_id]
|
|
138
|
+
|
|
139
|
+
config = {provider: {provider_name => {}}}
|
|
140
|
+
config[:model] = "#{provider_name}/#{model_id}" if model_id
|
|
141
|
+
|
|
142
|
+
config.to_json
|
|
137
143
|
end
|
|
138
144
|
|
|
139
145
|
def error_patterns
|