agent-harness 0.44.27 → 0.44.28
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 +7 -0
- data/README.md +11 -2
- data/lib/agent_harness/api/chat_transport.rb +7 -1
- data/lib/agent_harness/provider_health_check.rb +18 -2
- data/lib/agent_harness/providers/opencode.rb +72 -0
- 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: e80d5408a298ecc69d9bb926850bbf11affb491c0df7cbc466196c26efbbc719
|
|
4
|
+
data.tar.gz: 28268cff5cf59d3aea8c38f5eca82231b531bcdf41b289e9d14ff5720ad2fbc8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f77240dc395e73f9eef1e052d027ee4207720e709ef378b419637c929d2ae0d3eb26004e7b440a8d6638ab8243febe43439416b1182eb1db861bc96e121e902d
|
|
7
|
+
data.tar.gz: cd98f25ef91e81e9636f7e70e2cdf3fe884085fde1781dc60e6593c99078e74fc6dd41aaa9f9972c99c7ab3b9f0a83f1847c6f57a8961dcc6c1b6ba09cad9bdc
|
data/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
|
|
13
13
|
* close the RDR-072 upstream epic from #437's retained-scope evidence while keeping unpublished capabilities and downstream adoption behind their explicit release and integration gates ([#430](https://github.com/viamin/agent-harness/issues/430)).
|
|
14
14
|
|
|
15
|
+
## [0.44.28](https://github.com/viamin/agent-harness/compare/agent-harness/v0.44.27...agent-harness/v0.44.28) (2026-10-28)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **opencode:** surface MiniMax Token Plan HTTP 429 during preflight ([#518](https://github.com/viamin/agent-harness/issues/518)) ([d6f6288](https://github.com/viamin/agent-harness/commit/d6f6288f239cba6ac241edf87987bec22300a7b4))
|
|
21
|
+
|
|
15
22
|
## [0.44.27](https://github.com/viamin/agent-harness/compare/agent-harness/v0.44.26...agent-harness/v0.44.27) (2026-10-25)
|
|
16
23
|
|
|
17
24
|
|
data/README.md
CHANGED
|
@@ -834,7 +834,13 @@ results.each do |r|
|
|
|
834
834
|
end
|
|
835
835
|
|
|
836
836
|
# Check a single provider
|
|
837
|
-
|
|
837
|
+
minimax_runtime = AgentHarness::ProviderRuntime.new(
|
|
838
|
+
api_provider: "minimax",
|
|
839
|
+
model: "MiniMax-M3",
|
|
840
|
+
base_url: ENV.fetch("MINIMAX_BASE_URL"),
|
|
841
|
+
env: {"MINIMAX_API_KEY" => ENV.fetch("MINIMAX_API_KEY")}
|
|
842
|
+
)
|
|
843
|
+
result = AgentHarness.check_provider(:opencode, provider_runtime: minimax_runtime)
|
|
838
844
|
puts result[:status] # => "ok", "degraded", or "error"
|
|
839
845
|
|
|
840
846
|
# Formatted CLI output
|
|
@@ -847,8 +853,11 @@ Each result is a hash with keys:
|
|
|
847
853
|
- `:status` — `"ok"` (all checks passed), `"degraded"` (partial issues such as unimplemented auth status), or `"error"` (provider unavailable or authentication failed)
|
|
848
854
|
- `:message` — human-readable description
|
|
849
855
|
- `:latency_ms` — time taken for the check in milliseconds
|
|
856
|
+
- `:error_category` — structured failure category such as `:rate_limit`, `:authentication`, or `:timeout`
|
|
857
|
+
- `:check` — the check that produced the result, such as `:preflight` or `:smoke_test`
|
|
858
|
+
- `:reset_at` — provider-supplied rate-limit reset time when one is available; otherwise omitted
|
|
850
859
|
|
|
851
|
-
Health checks
|
|
860
|
+
Health checks cover registration, CLI availability, authentication, provider health, configuration, provider-owned preflight, and a bounded smoke test. For direct MiniMax OpenCode runtimes, preflight makes one no-retry provider request so an upstream HTTP 429 is returned as `error_category: :rate_limit` before OpenCode's smoke timeout. The default timeout per provider is configurable via `orchestration.health_check.timeout` (default: 5 seconds).
|
|
852
861
|
|
|
853
862
|
## Development
|
|
854
863
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "securerandom"
|
|
4
|
+
require "time"
|
|
4
5
|
require "ruby_llm"
|
|
5
6
|
require_relative "attempt_report"
|
|
6
7
|
require_relative "provider_error_detail"
|
|
@@ -499,7 +500,12 @@ module AgentHarness
|
|
|
499
500
|
headers = error.response.respond_to?(:headers) ? error.response.headers : error.response[:response_headers]
|
|
500
501
|
value = headers&.find { |key, _| key.to_s.casecmp?("retry-after") }&.last
|
|
501
502
|
delay = Float(value, exception: false)
|
|
502
|
-
delay if delay&.finite? && delay >= 0
|
|
503
|
+
return delay if delay&.finite? && delay >= 0
|
|
504
|
+
|
|
505
|
+
delay = Time.httpdate(value.to_s) - Time.now
|
|
506
|
+
[delay, 0].max
|
|
507
|
+
rescue ArgumentError, TypeError
|
|
508
|
+
nil
|
|
503
509
|
end
|
|
504
510
|
private_class_method :retry_after_seconds
|
|
505
511
|
|
|
@@ -281,7 +281,12 @@ module AgentHarness
|
|
|
281
281
|
# when only the host cannot reach the endpoint.
|
|
282
282
|
if provider_preflight_allowed
|
|
283
283
|
preflight_env = build_preflight_env(provider_instance, provider_runtime)
|
|
284
|
-
preflight =
|
|
284
|
+
preflight = run_provider_preflight(
|
|
285
|
+
provider_instance,
|
|
286
|
+
env: preflight_env,
|
|
287
|
+
timeout: timeout,
|
|
288
|
+
provider_runtime: provider_runtime
|
|
289
|
+
)
|
|
285
290
|
unless preflight[:healthy]
|
|
286
291
|
return build_result(
|
|
287
292
|
name: provider_name,
|
|
@@ -289,7 +294,8 @@ module AgentHarness
|
|
|
289
294
|
message: preflight[:reason] || "Preflight check failed",
|
|
290
295
|
start_time: start_time,
|
|
291
296
|
error_category: normalize_preflight_error_category(preflight[:error_category]),
|
|
292
|
-
check: :preflight
|
|
297
|
+
check: :preflight,
|
|
298
|
+
reset_at: preflight[:reset_at]
|
|
293
299
|
)
|
|
294
300
|
end
|
|
295
301
|
end
|
|
@@ -485,6 +491,16 @@ module AgentHarness
|
|
|
485
491
|
effective_executor.is_a?(CommandExecutor) && !effective_executor.is_a?(DockerCommandExecutor)
|
|
486
492
|
end
|
|
487
493
|
|
|
494
|
+
def run_provider_preflight(provider, env:, timeout:, provider_runtime:)
|
|
495
|
+
parameters = provider.method(:preflight_check).parameters
|
|
496
|
+
accepts_runtime = parameters.any? do |type, name|
|
|
497
|
+
type == :keyrest || (%i[key keyreq].include?(type) && name == :provider_runtime)
|
|
498
|
+
end
|
|
499
|
+
options = {env: env, timeout: timeout}
|
|
500
|
+
options[:provider_runtime] = provider_runtime if accepts_runtime
|
|
501
|
+
provider.preflight_check(**options)
|
|
502
|
+
end
|
|
503
|
+
|
|
488
504
|
def runtime_sensitive_host_overrides?(runtime)
|
|
489
505
|
runtime && (!runtime.env.empty? || !runtime.unset_env.empty? || runtime.base_url || runtime.api_provider)
|
|
490
506
|
end
|
|
@@ -165,6 +165,11 @@ module AgentHarness
|
|
|
165
165
|
.freeze
|
|
166
166
|
}.freeze
|
|
167
167
|
|
|
168
|
+
def initialize(preflight_transport: Api::ChatTransport.new, **options)
|
|
169
|
+
super(**options)
|
|
170
|
+
@preflight_transport = preflight_transport
|
|
171
|
+
end
|
|
172
|
+
|
|
168
173
|
def name
|
|
169
174
|
"opencode"
|
|
170
175
|
end
|
|
@@ -243,6 +248,21 @@ module AgentHarness
|
|
|
243
248
|
}
|
|
244
249
|
end
|
|
245
250
|
|
|
251
|
+
# OpenCode can silently retry MiniMax subscription limits until the CLI
|
|
252
|
+
# smoke timeout. Probe the same direct upstream once so callers receive a
|
|
253
|
+
# structured provider state without parsing CLI output.
|
|
254
|
+
def preflight_check(env:, timeout: 10, provider_runtime: nil)
|
|
255
|
+
runtime = ProviderRuntime.wrap(provider_runtime)
|
|
256
|
+
return {healthy: true} unless minimax_preflight?(runtime)
|
|
257
|
+
|
|
258
|
+
result = @preflight_transport.call(minimax_preflight_request(runtime, env, timeout))
|
|
259
|
+
return {healthy: true} if result[:status] == :succeeded
|
|
260
|
+
|
|
261
|
+
minimax_preflight_failure(result[:error])
|
|
262
|
+
rescue ArgumentError => e
|
|
263
|
+
{healthy: false, reason: e.message, error_category: :configuration}
|
|
264
|
+
end
|
|
265
|
+
|
|
246
266
|
protected
|
|
247
267
|
|
|
248
268
|
def build_command(prompt, options)
|
|
@@ -290,6 +310,58 @@ module AgentHarness
|
|
|
290
310
|
|
|
291
311
|
private
|
|
292
312
|
|
|
313
|
+
def minimax_preflight?(runtime)
|
|
314
|
+
runtime && runtime.api_provider.to_s.casecmp?("minimax")
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def minimax_preflight_request(runtime, env, timeout)
|
|
318
|
+
{
|
|
319
|
+
request_id: "opencode-minimax-preflight",
|
|
320
|
+
operation: :chat,
|
|
321
|
+
candidates: [minimax_candidate(runtime, env)],
|
|
322
|
+
messages: [{role: :user, content: [{type: :text, text: "Reply with exactly OK."}]}],
|
|
323
|
+
tools: [],
|
|
324
|
+
max_output_tokens: 8,
|
|
325
|
+
timeout: {read_seconds: timeout},
|
|
326
|
+
retry: {max_attempts: 1}
|
|
327
|
+
}
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def minimax_candidate(runtime, env)
|
|
331
|
+
endpoint = runtime.chat_base_url || runtime.base_url
|
|
332
|
+
model = runtime.chat_model || runtime.model
|
|
333
|
+
api_key = runtime.chat_api_key || env["MINIMAX_API_KEY"] || env["ANTHROPIC_API_KEY"]
|
|
334
|
+
raise ArgumentError, "MiniMax preflight requires an API endpoint" if endpoint.to_s.empty?
|
|
335
|
+
raise ArgumentError, "MiniMax preflight requires a model" if model.to_s.empty?
|
|
336
|
+
raise ArgumentError, "MiniMax preflight requires an API credential" if api_key.to_s.empty?
|
|
337
|
+
|
|
338
|
+
{
|
|
339
|
+
provider: :anthropic, protocol: :messages, authentication_mode: :api_key,
|
|
340
|
+
model: model, endpoint: endpoint, credentials: {api_key: api_key}
|
|
341
|
+
}
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def minimax_preflight_failure(error)
|
|
345
|
+
return minimax_rate_limit_failure(error) if error&.dig(:code)&.to_sym == :rate_limited
|
|
346
|
+
|
|
347
|
+
{
|
|
348
|
+
healthy: false,
|
|
349
|
+
reason: "MiniMax preflight request failed",
|
|
350
|
+
error_category: error&.dig(:category) || :transient
|
|
351
|
+
}
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def minimax_rate_limit_failure(error)
|
|
355
|
+
result = {
|
|
356
|
+
healthy: false,
|
|
357
|
+
reason: "MiniMax Token Plan rate limit reached",
|
|
358
|
+
error_category: :rate_limited
|
|
359
|
+
}
|
|
360
|
+
retry_after = error[:retry_after_seconds]
|
|
361
|
+
result[:reset_at] = Time.now.utc + retry_after if retry_after
|
|
362
|
+
result
|
|
363
|
+
end
|
|
364
|
+
|
|
293
365
|
def opencode_config_payload(runtime)
|
|
294
366
|
metadata = runtime.metadata
|
|
295
367
|
config_extras = metadata[:config] || metadata["config"] || {}
|