agent-harness 0.5.7 → 0.5.9
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/AUDIT_DISPOSITION.md +111 -0
- data/CHANGELOG.md +21 -0
- data/README.md +140 -2
- data/lib/agent_harness/authentication.rb +28 -9
- data/lib/agent_harness/command_executor.rb +450 -13
- data/lib/agent_harness/docker_command_executor.rb +499 -8
- data/lib/agent_harness/error_taxonomy.rb +4 -4
- data/lib/agent_harness/execution_preparation.rb +64 -0
- data/lib/agent_harness/orchestration/provider_manager.rb +26 -6
- data/lib/agent_harness/provider_health_check.rb +28 -6
- data/lib/agent_harness/provider_runtime.rb +38 -11
- data/lib/agent_harness/providers/adapter.rb +596 -8
- data/lib/agent_harness/providers/aider.rb +71 -0
- data/lib/agent_harness/providers/anthropic.rb +110 -7
- data/lib/agent_harness/providers/base.rb +34 -5
- data/lib/agent_harness/providers/codex.rb +40 -9
- data/lib/agent_harness/providers/cursor.rb +76 -2
- data/lib/agent_harness/providers/gemini.rb +21 -6
- data/lib/agent_harness/providers/github_copilot.rb +12 -0
- data/lib/agent_harness/providers/kilocode.rb +16 -1
- data/lib/agent_harness/providers/mistral_vibe.rb +9 -0
- data/lib/agent_harness/providers/opencode.rb +64 -3
- data/lib/agent_harness/providers/registry.rb +392 -18
- data/lib/agent_harness/version.rb +1 -1
- data/lib/agent_harness.rb +29 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f476f6224ed25cc79c7bc9c8fb239c1df9f787cd59ff279b8c509b7d515d2727
|
|
4
|
+
data.tar.gz: c0faee9c6d5c139db019707b9174d2d25a4a2fc4a85684ce55eb7baede9fd3be
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 113c0d8afbf5e77c6abcb1f78f0793646b72137e48a9776bfd951ee98c5412d3606628ea19ce4b105a6d8c05d1143a12a5607e1bad82462c629f6bdf3907985b
|
|
7
|
+
data.tar.gz: 4cc2f012c75314a2d096147e4bae9afbed565181878a907339410f7cfdb3ea0cff03e96704a6d3d93cd4f3825eb9f21a97d5351b852d72ec4e09b21a3359df68
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# PR Review Audit Disposition (Issue #91)
|
|
2
|
+
|
|
3
|
+
Audit of PRs merged before review completion in `viamin/agent-harness`.
|
|
4
|
+
|
|
5
|
+
## P1 PRs
|
|
6
|
+
|
|
7
|
+
### PR #55 — Per-request provider runtime overrides
|
|
8
|
+
|
|
9
|
+
| Finding | Disposition |
|
|
10
|
+
| ------- | ----------- |
|
|
11
|
+
| env stringifies only keys, not values | Fixed in PR #55 itself |
|
|
12
|
+
| Token tracking uses config model instead of runtime model | Fixed in PR #55 itself |
|
|
13
|
+
| nil env/metadata raises NoMethodError | Fixed in PR #55 itself |
|
|
14
|
+
| flags values not validated as Strings | Fixed in PR #55 itself |
|
|
15
|
+
| Adapter Hash-to-ProviderRuntime docs misleading | Fixed in PR #55 itself |
|
|
16
|
+
| `Array(flags)` freezes caller's array | Fixed in PR #55 itself |
|
|
17
|
+
| freezes caller-provided metadata Hash | Fixed in PR #55 itself |
|
|
18
|
+
| env type validation missing | Fixed in PR #55 itself |
|
|
19
|
+
| `.from_hash` no input type check | Fixed in PR #55 itself |
|
|
20
|
+
| flags accepts single String silently | Fixed in PR #55 itself |
|
|
21
|
+
| Cursor never normalizes/uses provider_runtime | Fixed in PR #55 itself |
|
|
22
|
+
| Cursor reimplements build_env instead of calling super | Fixed in PR #55 itself |
|
|
23
|
+
| runtime.model CLI vs telemetry mismatch | Fixed in PR #55 itself |
|
|
24
|
+
| Missing precedence spec | Fixed in PR #55 itself |
|
|
25
|
+
| `.wrap` kwargs bug on Ruby >= 3.2 | Fixed in PR #55 itself |
|
|
26
|
+
| runtime.model precedence unclear | Fixed in PR #55 itself |
|
|
27
|
+
| **base_url/model/api_provider not type-validated** | **Fixed forward in this PR** |
|
|
28
|
+
| **`.from_hash` treats `false` same as missing** | **Fixed forward in this PR** (hash_value helper) |
|
|
29
|
+
| Cursor not mentioned in PR description | Accepted (docs-only, no code impact) |
|
|
30
|
+
|
|
31
|
+
### PR #81 — Kilocode CLI installation contract
|
|
32
|
+
|
|
33
|
+
| Finding | Disposition |
|
|
34
|
+
| ------- | ----------- |
|
|
35
|
+
| `installation_contract` can raise NoMethodError for non-Adapter providers | Fixed in PR #81 itself |
|
|
36
|
+
| `provider_installation_contract` doesn't forward `version:` | Fixed in PR #81 itself |
|
|
37
|
+
| Default `installation_contract` doesn't accept `**options` | Fixed in PR #81 itself |
|
|
38
|
+
| Registry forwards `**options` unconditionally | Fixed in PR #81 itself |
|
|
39
|
+
| **`install_command` doesn't handle `version: nil`** | Accepted (produces clear error message via Gem::Version coercion) |
|
|
40
|
+
| **`validate_install_version!` gives generic error for bad input** | **Fixed forward in this PR** |
|
|
41
|
+
|
|
42
|
+
### PR #57 — Provider configuration capabilities
|
|
43
|
+
|
|
44
|
+
| Finding | Disposition |
|
|
45
|
+
| ------- | ----------- |
|
|
46
|
+
| All 19 findings (schema field removal, auth_modes derivation, model hints, accepts_arbitrary alignment) | All fixed in PR #57 itself across 5 review rounds |
|
|
47
|
+
|
|
48
|
+
### PR #42 — Gemini and Codex health/auth checks
|
|
49
|
+
|
|
50
|
+
| Finding | Disposition |
|
|
51
|
+
| ------- | ----------- |
|
|
52
|
+
| Temp dir from Dir.mktmpdir never cleaned up | Fixed in PR #42 itself |
|
|
53
|
+
| Config-file API key not validated for sk- prefix | Fixed in PR #42 itself |
|
|
54
|
+
| Error message hardcodes ~/.codex/config.json | Fixed in PR #42 itself |
|
|
55
|
+
| default_flags assumed to be Array, no guard | Fixed in PR #42 itself |
|
|
56
|
+
| Error message only mentions GEMINI_API_KEY | Fixed in PR #42 itself |
|
|
57
|
+
| validate_config doesn't check model/flags types | Fixed in PR #42 itself |
|
|
58
|
+
| JSON parse error leaks credential file contents | Fixed in PR #42 itself |
|
|
59
|
+
| Blank GEMINI_API_KEY doesn't fall back | Fixed in PR #42 itself |
|
|
60
|
+
| validate_config validates default_flags but build_command never uses them | Fixed in PR #42 itself |
|
|
61
|
+
| build_command raises if default_flags is non-Array | Fixed in PR #42 itself |
|
|
62
|
+
| auth_status assumes parsed JSON is Hash | Fixed in PR #42 itself |
|
|
63
|
+
| **auth_status returns inconsistent hash shape (missing auth_method)** | **Fixed forward in this PR** (both Codex and Gemini) |
|
|
64
|
+
| **Numeric status-code regexes can match unrelated numbers** | **Fixed forward in this PR** (added `\b` word boundaries) |
|
|
65
|
+
|
|
66
|
+
## P2 PRs
|
|
67
|
+
|
|
68
|
+
### PR #16 — DockerCommandExecutor
|
|
69
|
+
|
|
70
|
+
| Finding | Disposition |
|
|
71
|
+
| ------- | ----------- |
|
|
72
|
+
| Duplicated `normalize_command` method | Already resolved (inherits from protected parent) |
|
|
73
|
+
| `which` missing timeout | Already resolved (timeout: 5 added) |
|
|
74
|
+
| **container_id not validated for whitespace-only** | **Fixed forward in this PR** |
|
|
75
|
+
| Test stubs don't assert command arguments | Accepted (test quality, not a runtime defect) |
|
|
76
|
+
|
|
77
|
+
### PR #83 — OpenCode CLI installation contract
|
|
78
|
+
|
|
79
|
+
| Finding | Disposition |
|
|
80
|
+
| ------- | ----------- |
|
|
81
|
+
| No unresolved review comments | No action needed |
|
|
82
|
+
|
|
83
|
+
### PR #80 — Gemini CLI installation contract
|
|
84
|
+
|
|
85
|
+
| Finding | Disposition |
|
|
86
|
+
| ------- | ----------- |
|
|
87
|
+
| `install_contract` signature mismatch between base adapter and callers | Already resolved in current adapter.rb |
|
|
88
|
+
| No guard for providers without `install_contract` | Already resolved in registry.rb |
|
|
89
|
+
| **Malformed version strings not handled gracefully** | Already resolved (Gemini rescues ArgumentError from Gem::Version) |
|
|
90
|
+
| Missing test coverage for edge cases | Accepted (test quality, not a runtime defect) |
|
|
91
|
+
|
|
92
|
+
### PR #1 — Initial extraction
|
|
93
|
+
|
|
94
|
+
| Finding | Disposition |
|
|
95
|
+
| ------- | ----------- |
|
|
96
|
+
| `timecop` gem not declared in Gemfile | Already resolved (timecop removed from codebase) |
|
|
97
|
+
| Missing YamlLoader/EnvLoader files | Already resolved (references removed) |
|
|
98
|
+
| README binary name for Cursor incorrect | Already resolved (README says cursor-agent, matches code) |
|
|
99
|
+
| README binary name for GitHub Copilot incorrect | Already resolved (README updated) |
|
|
100
|
+
| bin/ excluded from gemspec | Accepted (intentional for gem packaging) |
|
|
101
|
+
|
|
102
|
+
## Summary of fixes in this PR
|
|
103
|
+
|
|
104
|
+
1. **ProviderRuntime**: Added type validation for `model`, `base_url`, `api_provider` (must be String or nil)
|
|
105
|
+
2. **ProviderRuntime**: Replaced `||`-based hash key lookup in `.from_hash` with `hash_value` helper that distinguishes nil from missing keys
|
|
106
|
+
3. **Codex auth_status**: Added consistent `auth_method` key to all return paths
|
|
107
|
+
4. **Gemini auth_status**: Added consistent `auth_method` key to all return paths
|
|
108
|
+
5. **Codex error_patterns**: Added `\b` word boundaries to `/401/` regex
|
|
109
|
+
6. **Gemini error_patterns**: Added `\b` word boundaries to `/429/` and `/503/` regexes
|
|
110
|
+
7. **Kilocode**: `validate_install_version!` now rescues malformed version strings with a provider-specific error message
|
|
111
|
+
8. **DockerCommandExecutor**: Validates whitespace-only `container_id` values
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.9](https://github.com/viamin/agent-harness/compare/agent-harness/v0.5.8...agent-harness/v0.5.9) (2026-04-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* 91: audit and fix-forward defects from PRs merged before review completion ([ec0af12](https://github.com/viamin/agent-harness/commit/ec0af12728f6524b651ebc9b552c477059a2adac))
|
|
9
|
+
* **providers:** close path traversal bypass in env-var-prefixed preparation paths ([493bf55](https://github.com/viamin/agent-harness/commit/493bf55be088a8bde39d143791f3f9b0c90d9c0c))
|
|
10
|
+
* **providers:** harden preparation path validation against env value traversal and command substitution ([ed4b92b](https://github.com/viamin/agent-harness/commit/ed4b92b8760ec4dfa1f50d69b0736563140905b4))
|
|
11
|
+
* **providers:** harden preparation path validation against injection and traversal ([9cb0faf](https://github.com/viamin/agent-harness/commit/9cb0fafbf63c908c7ef7b465bf541ceb5dbf58bb))
|
|
12
|
+
* **providers:** preserve docker idle timeouts ([adde6b4](https://github.com/viamin/agent-harness/commit/adde6b4491023c8baf37249147770cddeff1b0ee))
|
|
13
|
+
|
|
14
|
+
## [0.5.8](https://github.com/viamin/agent-harness/compare/agent-harness/v0.5.7...agent-harness/v0.5.8) (2026-04-07)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* 64: feat(installers): make Claude CLI installation/version support a first-class provider contract ([#78](https://github.com/viamin/agent-harness/issues/78)) ([0d83590](https://github.com/viamin/agent-harness/commit/0d83590dc9bb9bc7c8d621660bcd73b8eb613d43))
|
|
20
|
+
* 70: feat(installers): make Cursor agent CLI installation/version support a first-class provider contract ([#82](https://github.com/viamin/agent-harness/issues/82)) ([483e9be](https://github.com/viamin/agent-harness/commit/483e9be752de9548020135a86170d978d2a23ae8))
|
|
21
|
+
* 71: feat(installers): make Aider CLI installation/version support a first-class provider contract ([#84](https://github.com/viamin/agent-harness/issues/84)) ([3cfcc1c](https://github.com/viamin/agent-harness/commit/3cfcc1cac831060c12fd8a39130ee7bb9b048aa5))
|
|
22
|
+
* 74: feat(providers): expose provider capability/installability/auth metadata for downstream apps ([#88](https://github.com/viamin/agent-harness/issues/88)) ([4f9b3ba](https://github.com/viamin/agent-harness/commit/4f9b3ba6a53c830eaf53e9233e8df38970c52c8c))
|
|
23
|
+
|
|
3
24
|
## [0.5.7](https://github.com/viamin/agent-harness/compare/agent-harness/v0.5.6...agent-harness/v0.5.7) (2026-04-07)
|
|
4
25
|
|
|
5
26
|
|
data/README.md
CHANGED
|
@@ -5,7 +5,7 @@ A unified Ruby interface for CLI-based AI coding agents like Claude Code, Cursor
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Unified Interface**: Single API for multiple AI coding agents
|
|
8
|
-
- **
|
|
8
|
+
- **9 Built-in Providers**: Claude Code, Cursor, Gemini CLI, GitHub Copilot, Codex, Aider, OpenCode, Kilocode, Mistral Vibe
|
|
9
9
|
- **Full Orchestration**: Provider switching, circuit breakers, rate limiting, and health monitoring
|
|
10
10
|
- **Flexible Configuration**: YAML, Ruby DSL, or environment variables
|
|
11
11
|
- **Token Tracking**: Monitor usage across providers for cost and limit management
|
|
@@ -107,6 +107,7 @@ end
|
|
|
107
107
|
| `:aider` | `aider` | Aider coding assistant |
|
|
108
108
|
| `:opencode` | `opencode` | OpenCode CLI |
|
|
109
109
|
| `:kilocode` | `kilo` | Kilocode CLI |
|
|
110
|
+
| `:mistral_vibe` | `mistral-vibe` | Mistral Vibe CLI |
|
|
110
111
|
|
|
111
112
|
### Provider Install Contracts
|
|
112
113
|
|
|
@@ -126,6 +127,41 @@ contract[:install_command]
|
|
|
126
127
|
# => ["npm", "install", "-g", "--ignore-scripts", "@google/gemini-cli@0.35.3"]
|
|
127
128
|
```
|
|
128
129
|
|
|
130
|
+
Cursor also exposes a first-class install contract for container/image builds.
|
|
131
|
+
The contract publishes checksums for both the installer script and the default
|
|
132
|
+
Linux x64 artifact so consumers can verify downloads independently:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
cursor_install = AgentHarness::Providers::Cursor.install_metadata
|
|
136
|
+
|
|
137
|
+
cursor_install
|
|
138
|
+
# => {
|
|
139
|
+
# source: {
|
|
140
|
+
# type: :shell_script,
|
|
141
|
+
# url: "https://cursor.com/install",
|
|
142
|
+
# resolved_version: "2026.03.30-a5d3e17",
|
|
143
|
+
# default_artifact_url: "https://downloads.cursor.com/lab/2026.03.30-a5d3e17/linux/x64/agent-cli-package.tar.gz"
|
|
144
|
+
# },
|
|
145
|
+
# checksum: {
|
|
146
|
+
# strategy: :sha256,
|
|
147
|
+
# targets: {
|
|
148
|
+
# script: { url: "https://cursor.com/install", value: "8371..." },
|
|
149
|
+
# artifacts: { "linux/x64" => { url: "https://downloads.cursor.com/...", value: "e0d4..." } }
|
|
150
|
+
# }
|
|
151
|
+
# },
|
|
152
|
+
# binary: {
|
|
153
|
+
# name: "cursor-agent",
|
|
154
|
+
# path: "$HOME/.local/bin/cursor-agent",
|
|
155
|
+
# suggested_global_path: "/usr/local/bin/cursor-agent"
|
|
156
|
+
# },
|
|
157
|
+
# version: {
|
|
158
|
+
# default: "latest",
|
|
159
|
+
# supported: "latest",
|
|
160
|
+
# command: ["cursor-agent", "--version"]
|
|
161
|
+
# }
|
|
162
|
+
# }
|
|
163
|
+
```
|
|
164
|
+
|
|
129
165
|
### Direct Provider Access
|
|
130
166
|
|
|
131
167
|
```ruby
|
|
@@ -138,11 +174,28 @@ if AgentHarness::Providers::Registry.instance.get(:claude).available?
|
|
|
138
174
|
puts "Claude CLI is installed"
|
|
139
175
|
end
|
|
140
176
|
|
|
177
|
+
# Ask the harness which Claude CLI install contract it supports
|
|
178
|
+
contract = AgentHarness.install_contract(:claude)
|
|
179
|
+
puts contract[:install][:command]
|
|
180
|
+
# => "tmp_script=$(mktemp) && ... && bash \"$tmp_script\" 2.1.92"
|
|
181
|
+
puts contract[:install][:post_install_binary_path]
|
|
182
|
+
# => "$HOME/.local/bin/claude"
|
|
183
|
+
puts contract[:supported_versions][:default]
|
|
184
|
+
# => "2.1.92"
|
|
185
|
+
puts contract[:supported_versions][:requirement]
|
|
186
|
+
# => ">= 2.1.92, < 2.2.0"
|
|
187
|
+
|
|
141
188
|
# List all registered providers
|
|
142
189
|
AgentHarness::Providers::Registry.instance.all
|
|
143
|
-
# => [:claude, :cursor, :gemini, :github_copilot, :codex, :opencode, :kilocode, :aider]
|
|
190
|
+
# => [:claude, :cursor, :gemini, :github_copilot, :codex, :opencode, :kilocode, :aider, :mistral_vibe]
|
|
144
191
|
```
|
|
145
192
|
|
|
193
|
+
For Claude, the install contract is the first-class source of truth for:
|
|
194
|
+
|
|
195
|
+
- the official install recipe the current harness release expects
|
|
196
|
+
- the expected binary name and normalized PATH entry that recipe leaves behind
|
|
197
|
+
- the supported Claude CLI version boundary the current harness release validates (`default` plus the compatible version range)
|
|
198
|
+
|
|
146
199
|
### Provider Installation Contracts
|
|
147
200
|
|
|
148
201
|
Downstream apps can ask `agent-harness` for provider-specific CLI install
|
|
@@ -170,6 +223,8 @@ Providers that expose installation contracts can also be queried through the
|
|
|
170
223
|
generic API:
|
|
171
224
|
|
|
172
225
|
```ruby
|
|
226
|
+
codex_install = AgentHarness.installation_contract(:codex)
|
|
227
|
+
aider_install = AgentHarness.installation_contract(:aider)
|
|
173
228
|
opencode_install = AgentHarness.installation_contract(:opencode)
|
|
174
229
|
|
|
175
230
|
opencode_install
|
|
@@ -181,11 +236,94 @@ opencode_install
|
|
|
181
236
|
# binary_name: "opencode",
|
|
182
237
|
# install_command: ["npm", "install", "-g", "--ignore-scripts", "opencode-ai@1.3.2"]
|
|
183
238
|
# }
|
|
239
|
+
|
|
240
|
+
aider_install
|
|
241
|
+
# => {
|
|
242
|
+
# source: :uv_tool,
|
|
243
|
+
# bootstrap_package: "uv==0.8.17",
|
|
244
|
+
# package_name: "aider-chat",
|
|
245
|
+
# version: "0.86.2",
|
|
246
|
+
# binary_name: "aider",
|
|
247
|
+
# binary_path: "/usr/local/bin/aider",
|
|
248
|
+
# install_environment: {
|
|
249
|
+
# "UV_TOOL_BIN_DIR" => "/usr/local/bin",
|
|
250
|
+
# "UV_TOOL_DIR" => "/opt/uv/tools",
|
|
251
|
+
# "UV_PYTHON_INSTALL_DIR" => "/opt/uv/python"
|
|
252
|
+
# },
|
|
253
|
+
# bootstrap_commands: [
|
|
254
|
+
# ["python3", "-m", "pip", "install", "--no-cache-dir", "--break-system-packages", "uv==0.8.17"]
|
|
255
|
+
# ],
|
|
256
|
+
# install_command: ["uv", "tool", "install", "--force", "--python", "python3.12", "--with", "pip", "aider-chat==0.86.2"]
|
|
257
|
+
# }
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
For supported providers like Codex and Aider, the install contract tracks
|
|
261
|
+
the CLI version supported by the current `agent-harness` release. The
|
|
262
|
+
contract includes bootstrap requirements, install command shape, and the
|
|
263
|
+
expected runtime binary, and the provider specs assert that runtime
|
|
264
|
+
expectations remain aligned with the published install metadata.
|
|
265
|
+
|
|
266
|
+
### Provider Metadata
|
|
267
|
+
|
|
268
|
+
Downstream apps can also query a consolidated provider metadata contract for
|
|
269
|
+
configuration UIs and policy decisions.
|
|
270
|
+
|
|
271
|
+
The following example shows how to retrieve metadata for the Anthropic provider:
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
metadata = AgentHarness.provider_metadata(:anthropic)
|
|
275
|
+
|
|
276
|
+
metadata
|
|
277
|
+
# => {
|
|
278
|
+
# provider: :claude,
|
|
279
|
+
# canonical_provider: :claude,
|
|
280
|
+
# aliases: [:anthropic],
|
|
281
|
+
# auth: {
|
|
282
|
+
# default_mode: :oauth,
|
|
283
|
+
# supported_modes: [:oauth],
|
|
284
|
+
# service: :anthropic,
|
|
285
|
+
# api_family: :anthropic
|
|
286
|
+
# },
|
|
287
|
+
# runtime: {
|
|
288
|
+
# interface: :cli,
|
|
289
|
+
# requires_cli: true,
|
|
290
|
+
# installable: false,
|
|
291
|
+
# installation: nil,
|
|
292
|
+
# supports_mcp: true,
|
|
293
|
+
# supports_dangerous_mode: true
|
|
294
|
+
# },
|
|
295
|
+
# health_check: {
|
|
296
|
+
# supports_registry_checks: true,
|
|
297
|
+
# auth_check_supported: true,
|
|
298
|
+
# lightweight: true
|
|
299
|
+
# },
|
|
300
|
+
# identity: {
|
|
301
|
+
# bot_usernames: ["claude", "anthropic"]
|
|
302
|
+
# }
|
|
303
|
+
# }
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
To enumerate the full catalog:
|
|
307
|
+
|
|
308
|
+
```ruby
|
|
309
|
+
AgentHarness.provider_metadata_catalog
|
|
310
|
+
# => { claude: {...}, cursor: {...}, gemini: {...}, ... }
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Provider metadata is cached so repeated catalog reads stay cheap.
|
|
314
|
+
Pass `refresh: true` to rebuild metadata and re-run live availability checks when needed:
|
|
315
|
+
|
|
316
|
+
```ruby
|
|
317
|
+
AgentHarness.provider_metadata(:anthropic, refresh: true)
|
|
318
|
+
AgentHarness.provider_metadata_catalog(refresh: true)
|
|
184
319
|
```
|
|
185
320
|
|
|
186
321
|
For providers with install contracts, the metadata tracks the CLI version
|
|
187
322
|
supported by the current `agent-harness` release, and the runtime adapter
|
|
188
323
|
tests assert that the expected binary remains aligned with that contract.
|
|
324
|
+
`runtime[:installation]` is normalized to a stable shape with
|
|
325
|
+
`source_type`, `package_name`, version fields, and install commands so
|
|
326
|
+
downstream apps do not need provider-specific branching.
|
|
189
327
|
|
|
190
328
|
### Custom Providers
|
|
191
329
|
|
|
@@ -94,15 +94,26 @@ module AgentHarness
|
|
|
94
94
|
|
|
95
95
|
def resolve_provider(provider_name)
|
|
96
96
|
klass = Providers::Registry.instance.get(provider_name)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
executor:
|
|
104
|
-
|
|
105
|
-
|
|
97
|
+
canonical_name = Providers::Registry.instance.canonical_name(provider_name)
|
|
98
|
+
config = provider_config_for(provider_name, canonical_name: canonical_name)
|
|
99
|
+
executor = AgentHarness.configuration.command_executor
|
|
100
|
+
logger = AgentHarness.logger
|
|
101
|
+
|
|
102
|
+
provider = if klass.respond_to?(:build_provider_instance, true)
|
|
103
|
+
klass.send(:build_provider_instance, config: config, executor: executor, logger: logger)
|
|
104
|
+
else
|
|
105
|
+
klass.new(config: config, executor: executor, logger: logger)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Ensure the executor is available even when the provider constructor
|
|
109
|
+
# accepts only a subset of keywords (e.g. config: only).
|
|
110
|
+
if provider.respond_to?(:executor=) && provider.executor.nil?
|
|
111
|
+
provider.executor = executor
|
|
112
|
+
elsif !provider.respond_to?(:executor)
|
|
113
|
+
provider.define_singleton_method(:executor) { executor }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
provider
|
|
106
117
|
rescue ConfigurationError
|
|
107
118
|
raise ProviderNotFoundError, "Unknown provider: #{provider_name}"
|
|
108
119
|
end
|
|
@@ -152,6 +163,14 @@ module AgentHarness
|
|
|
152
163
|
"https://claude.ai/oauth/authorize"
|
|
153
164
|
end
|
|
154
165
|
|
|
166
|
+
def provider_config_for(requested_name, canonical_name:)
|
|
167
|
+
requested_key = requested_name.to_sym
|
|
168
|
+
canonical_key = canonical_name.to_sym
|
|
169
|
+
|
|
170
|
+
AgentHarness.configuration.providers[requested_key] ||
|
|
171
|
+
AgentHarness.configuration.providers[canonical_key]
|
|
172
|
+
end
|
|
173
|
+
|
|
155
174
|
def refresh_claude_auth(token: nil)
|
|
156
175
|
raise ArgumentError, "token must be a non-empty string" unless token.is_a?(String) && !token.strip.empty?
|
|
157
176
|
|