agent-harness 0.44.21 → 0.44.22
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/docs/provider-neutral-api-execution-contract.md +7 -1
- data/lib/agent_harness/api/chat_transport.rb +13 -3
- data/lib/agent_harness/api/provider_error_detail.rb +70 -0
- data/lib/agent_harness/api/ruby_llm_chat_adapter.rb +1 -1
- data/lib/agent_harness/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bd3556ae8d2b4d460344b7479ad613d978baec38b54a7261bc4024314b2a5fd6
|
|
4
|
+
data.tar.gz: e5db97cdc1f923b2791d80bd3ca7b3dcf1060adc30a0c979f96da94e93740f63
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 38cba9849f8f085fe92b6c9bb5a7de4df67d4c3591a6d6a48459d391aaf4dc79ccbd8dbc684d667e103df3b2e3661dc82da8e4d88df9327ccf231741c1058525
|
|
7
|
+
data.tar.gz: 2567485b7f2c3f181d61d9124d43c6e6dd13bdf3ac6158687e6b5ebd0f75a8506a9e0b5dc0157ccc4fa4ff9a06ee52483b0f7571c4442de8fc3bdc59cdaef712
|
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.22](https://github.com/viamin/agent-harness/compare/agent-harness/v0.44.21...agent-harness/v0.44.22) (2026-10-21)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* Preserve safe actionable provider error details in API chat failures ([#502](https://github.com/viamin/agent-harness/issues/502)) ([509a877](https://github.com/viamin/agent-harness/commit/509a8773e330cdc4bccb53edfe51bafef6b8e20f))
|
|
21
|
+
|
|
15
22
|
## [0.44.21](https://github.com/viamin/agent-harness/compare/agent-harness/v0.44.20...agent-harness/v0.44.21) (2026-10-21)
|
|
16
23
|
|
|
17
24
|
|
|
@@ -490,7 +490,13 @@ result/usage where available. Categories are:
|
|
|
490
490
|
|
|
491
491
|
Adapters MUST use the following category and code mappings. A provider-specific
|
|
492
492
|
status or error name may be retained as sanitized metadata, but MUST NOT replace
|
|
493
|
-
these values or change fallback eligibility.
|
|
493
|
+
these values or change fallback eligibility. The normalized `message` may append
|
|
494
|
+
bounded, actionable detail for request, capability, configuration, and
|
|
495
|
+
response-shape failures. Such detail MUST redact credentials, headers, request
|
|
496
|
+
bodies, conversation content, and URLs, and MUST never copy an unparsed provider
|
|
497
|
+
response body or a message derived from one. Parser diagnostics are also omitted
|
|
498
|
+
because they can quote request or conversation content. Attempt reports retain
|
|
499
|
+
the same sanitized error object as the public result.
|
|
494
500
|
|
|
495
501
|
| Failure | Category | Code |
|
|
496
502
|
| --- | --- | --- |
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
require "ruby_llm"
|
|
5
5
|
require_relative "attempt_report"
|
|
6
|
+
require_relative "provider_error_detail"
|
|
6
7
|
require_relative "ruby_llm_chat_adapter"
|
|
7
8
|
require_relative "schema_response"
|
|
8
9
|
|
|
@@ -443,6 +444,11 @@ module AgentHarness
|
|
|
443
444
|
end
|
|
444
445
|
|
|
445
446
|
module ErrorClassifier
|
|
447
|
+
ACTIONABLE_DETAIL_ERRORS = [
|
|
448
|
+
RubyLLM::BadRequestError,
|
|
449
|
+
RubyLlmChatAdapter::UnsupportedOptionError
|
|
450
|
+
].freeze
|
|
451
|
+
|
|
446
452
|
TRANSIENT = {
|
|
447
453
|
Faraday::TimeoutError => :timeout,
|
|
448
454
|
Faraday::ConnectionFailed => :connection_failed,
|
|
@@ -480,7 +486,7 @@ module AgentHarness
|
|
|
480
486
|
end
|
|
481
487
|
|
|
482
488
|
def self.payload(error, category, code, retryable)
|
|
483
|
-
payload = {category: category, code: code, retryable: retryable, message: safe_message(category, code)}
|
|
489
|
+
payload = {category: category, code: code, retryable: retryable, message: safe_message(error, category, code)}
|
|
484
490
|
retry_after = retry_after_seconds(error)
|
|
485
491
|
payload[:retry_after_seconds] = retry_after if retry_after
|
|
486
492
|
payload
|
|
@@ -497,8 +503,12 @@ module AgentHarness
|
|
|
497
503
|
end
|
|
498
504
|
private_class_method :retry_after_seconds
|
|
499
505
|
|
|
500
|
-
def self.safe_message(category, code)
|
|
501
|
-
"Chat request failed (#{category}/#{code})"
|
|
506
|
+
def self.safe_message(error, category, code)
|
|
507
|
+
summary = "Chat request failed (#{category}/#{code})"
|
|
508
|
+
return summary unless ACTIONABLE_DETAIL_ERRORS.any? { |klass| error.is_a?(klass) }
|
|
509
|
+
|
|
510
|
+
detail = ProviderErrorDetail.call(error)
|
|
511
|
+
detail.to_s.empty? ? summary : "#{summary}: #{detail}"
|
|
502
512
|
end
|
|
503
513
|
private_class_method :safe_message
|
|
504
514
|
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AgentHarness
|
|
4
|
+
module Api
|
|
5
|
+
# Extracts allowlisted, actionable text from provider errors without
|
|
6
|
+
# retaining response bodies or caller-controlled message content.
|
|
7
|
+
module ProviderErrorDetail
|
|
8
|
+
LOCAL_DETAILS = [
|
|
9
|
+
"RubyLLM does not support request-local connect timeouts",
|
|
10
|
+
"provider state does not match the selected protocol",
|
|
11
|
+
"provider state must carry at least one Responses output item",
|
|
12
|
+
"unsupported content type"
|
|
13
|
+
].freeze
|
|
14
|
+
TOOL_REASONING_DETAIL = "Function tools with reasoning_effort are not supported for gpt-5.6-luna " \
|
|
15
|
+
"in /v1/chat/completions. To use function tools, use /v1/responses or set reasoning_effort to 'none'."
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def call(error)
|
|
20
|
+
message = normalized(error.message)
|
|
21
|
+
return if message.empty? || structured_body?(message) || !safe_source?(error, message)
|
|
22
|
+
|
|
23
|
+
message
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def normalized(value)
|
|
27
|
+
value.to_s.encode("UTF-8", invalid: :replace, undef: :replace, replace: "�")
|
|
28
|
+
.gsub(/[[:cntrl:]]+/, " ").gsub(/\s+/, " ").strip
|
|
29
|
+
end
|
|
30
|
+
private_class_method :normalized
|
|
31
|
+
|
|
32
|
+
def safe_source?(error, message)
|
|
33
|
+
safe_local_detail?(error, message) || safe_provider_detail?(error, message)
|
|
34
|
+
end
|
|
35
|
+
private_class_method :safe_source?
|
|
36
|
+
|
|
37
|
+
def safe_local_detail?(error, message)
|
|
38
|
+
error.is_a?(RubyLlmChatAdapter::UnsupportedOptionError) && LOCAL_DETAILS.include?(message)
|
|
39
|
+
end
|
|
40
|
+
private_class_method :safe_local_detail?
|
|
41
|
+
|
|
42
|
+
# RubyLLM parses this provider diagnostic from a JSON error field before
|
|
43
|
+
# constructing BadRequestError. Checking both the structured body and its
|
|
44
|
+
# fixed, non-content-bearing shape avoids exposing raw bodies or arbitrary
|
|
45
|
+
# provider prose that may echo request or conversation content.
|
|
46
|
+
def safe_provider_detail?(error, message)
|
|
47
|
+
error.is_a?(RubyLLM::BadRequestError) && message == TOOL_REASONING_DETAIL &&
|
|
48
|
+
structured_messages(error.response).include?(message)
|
|
49
|
+
end
|
|
50
|
+
private_class_method :safe_provider_detail?
|
|
51
|
+
|
|
52
|
+
def structured_messages(response)
|
|
53
|
+
body = response&.body
|
|
54
|
+
parsed = body.is_a?(String) ? JSON.parse(body) : body
|
|
55
|
+
return [] unless parsed.is_a?(Hash)
|
|
56
|
+
|
|
57
|
+
error = parsed["error"] || parsed[:error]
|
|
58
|
+
[error, parsed].filter_map { |part| part["message"] || part[:message] if part.is_a?(Hash) }
|
|
59
|
+
rescue JSON::ParserError
|
|
60
|
+
[]
|
|
61
|
+
end
|
|
62
|
+
private_class_method :structured_messages
|
|
63
|
+
|
|
64
|
+
def structured_body?(message)
|
|
65
|
+
message.start_with?("{", "[", "<")
|
|
66
|
+
end
|
|
67
|
+
private_class_method :structured_body?
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -303,7 +303,7 @@ module AgentHarness
|
|
|
303
303
|
|
|
304
304
|
Array(content).map do |part|
|
|
305
305
|
unless part[:type]&.to_sym == :text
|
|
306
|
-
raise UnsupportedOptionError, "unsupported content type
|
|
306
|
+
raise UnsupportedOptionError, "unsupported content type"
|
|
307
307
|
end
|
|
308
308
|
|
|
309
309
|
part[:text].to_s
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: agent-harness
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.44.
|
|
4
|
+
version: 0.44.22
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bart Agapinan
|
|
@@ -145,6 +145,7 @@ files:
|
|
|
145
145
|
- lib/agent_harness.rb
|
|
146
146
|
- lib/agent_harness/api/attempt_report.rb
|
|
147
147
|
- lib/agent_harness/api/chat_transport.rb
|
|
148
|
+
- lib/agent_harness/api/provider_error_detail.rb
|
|
148
149
|
- lib/agent_harness/api/ruby_llm_chat_adapter.rb
|
|
149
150
|
- lib/agent_harness/api/schema_response.rb
|
|
150
151
|
- lib/agent_harness/authentication.rb
|