agent-harness 0.28.6 → 0.29.0
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 +3 -2
- data/lib/agent_harness/providers/adapter.rb +26 -0
- data/lib/agent_harness/providers/omp.rb +258 -0
- data/lib/agent_harness/providers/registry.rb +1 -0
- 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: e929940987f238b75b654eadf2d882b0a554fb755c3badac115c7ae218875677
|
|
4
|
+
data.tar.gz: a83fefa5de68968675304ed70316ccfa37a7b3063bd58a1edf3b8c493f017daf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a08f5ce50f0b0c200034d53908ded075808f7a2e374aff7365d54240cf1dc291d1cde28d4e1d4e76cc2d16273aa30bb17f81384243d626ce60fd5c5fd6d2f702
|
|
7
|
+
data.tar.gz: 615fec572434e6a0653f3ae3cd1723f551d7578eb30ef2f1ee0a2714c6221be8acd29c06e3830d10f0e88c040c31faab51658c26b09b79e18215f295587c644e
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
* **auth:** add provider-owned PKCE code-exchange API for Claude OAuth (`AgentHarness::Authentication.exchange_code`). Takes an authorization code plus PKCE verifier (and `redirect_uri`/`client_id`), posts an `authorization_code` grant to the Claude token endpoint, and persists the resulting access/refresh tokens in the native `claudeAiOauth` shape. Adds `exchange_code_supported?` and a `code_exchange` key to `auth_capabilities` ([#266](https://github.com/viamin/agent-harness/issues/266)).
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
## [0.29.0](https://github.com/viamin/agent-harness/compare/agent-harness/v0.28.6...agent-harness/v0.29.0) (2026-07-16)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* Add Oh My Pi provider metadata and install contract ([#298](https://github.com/viamin/agent-harness/issues/298)) ([3be62a8](https://github.com/viamin/agent-harness/commit/3be62a884f50ceeb903e199f4a9906cf3fecf014))
|
|
15
|
+
|
|
9
16
|
## [0.28.6](https://github.com/viamin/agent-harness/compare/agent-harness/v0.28.5...agent-harness/v0.28.6) (2026-07-12)
|
|
10
17
|
|
|
11
18
|
|
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
|
+
- **11 Built-in Providers**: Claude Code, Cursor, Gemini CLI, GitHub Copilot, Codex, Pi, Oh My Pi, 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
|
|
@@ -105,6 +105,7 @@ end
|
|
|
105
105
|
| `:github_copilot` | `copilot` | GitHub Copilot CLI |
|
|
106
106
|
| `:codex` | `codex` | OpenAI Codex CLI |
|
|
107
107
|
| `:pi` | `pi` | Pi coding agent CLI |
|
|
108
|
+
| `:omp` | `omp` | Oh My Pi coding agent CLI (Bun-based fork of Pi) |
|
|
108
109
|
| `:aider` | `aider` | Aider coding assistant |
|
|
109
110
|
| `:opencode` | `opencode` | OpenCode CLI |
|
|
110
111
|
| `:kilocode` | `kilo` | Kilocode CLI |
|
|
@@ -188,7 +189,7 @@ puts contract[:supported_versions][:requirement]
|
|
|
188
189
|
|
|
189
190
|
# List all registered providers
|
|
190
191
|
AgentHarness::Providers::Registry.instance.all
|
|
191
|
-
# => [:claude, :cursor, :gemini, :github_copilot, :pi, :codex, :opencode, :kilocode, :aider, :mistral_vibe]
|
|
192
|
+
# => [:claude, :cursor, :gemini, :github_copilot, :pi, :omp, :codex, :opencode, :kilocode, :aider, :mistral_vibe]
|
|
192
193
|
```
|
|
193
194
|
|
|
194
195
|
For Claude, the install contract is the first-class source of truth for:
|
|
@@ -39,9 +39,35 @@ module AgentHarness
|
|
|
39
39
|
normalized[:requires_postinstall] = contract[:requires_postinstall] if contract.key?(:requires_postinstall)
|
|
40
40
|
normalized[:postinstall_command] = contract[:postinstall_command] if contract.key?(:postinstall_command)
|
|
41
41
|
|
|
42
|
+
if contract.key?(:runtime_requirements)
|
|
43
|
+
normalized[:runtime_requirements] =
|
|
44
|
+
normalize_metadata_runtime_requirements(contract[:runtime_requirements])
|
|
45
|
+
end
|
|
46
|
+
|
|
42
47
|
normalized
|
|
43
48
|
end
|
|
44
49
|
|
|
50
|
+
# Normalize an installation contract's `runtime_requirements` so the
|
|
51
|
+
# stable provider-metadata path exposes the same runtime prerequisites
|
|
52
|
+
# (e.g. Bun for the :omp provider) as the raw installation contract.
|
|
53
|
+
#
|
|
54
|
+
# Each entry is duplicated so consumers receive a self-contained hash
|
|
55
|
+
# they can mutate top-level keys on without aliasing the provider-owned
|
|
56
|
+
# contract, and the resulting array is frozen to signal that the
|
|
57
|
+
# normalized metadata is read-only.
|
|
58
|
+
#
|
|
59
|
+
# @param requirements [Array<Hash>, nil]
|
|
60
|
+
# @return [Array<Hash>, nil] normalized requirements, or nil when absent
|
|
61
|
+
def self.normalize_metadata_runtime_requirements(requirements)
|
|
62
|
+
return requirements unless requirements.is_a?(Array)
|
|
63
|
+
|
|
64
|
+
requirements.map do |requirement|
|
|
65
|
+
next requirement.dup if requirement.is_a?(Hash)
|
|
66
|
+
|
|
67
|
+
requirement
|
|
68
|
+
end.freeze
|
|
69
|
+
end
|
|
70
|
+
|
|
45
71
|
def self.normalize_metadata_source_type(source)
|
|
46
72
|
return source[:type]&.to_sym if source.is_a?(Hash)
|
|
47
73
|
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubygems/requirement"
|
|
4
|
+
|
|
5
|
+
module AgentHarness
|
|
6
|
+
module Providers
|
|
7
|
+
# Oh My Pi coding agent CLI provider
|
|
8
|
+
#
|
|
9
|
+
# Provides integration with the Oh My Pi (can1357/oh-my-pi) terminal
|
|
10
|
+
# coding agent, a Bun-based fork of the Pi coding agent. Distinct from
|
|
11
|
+
# the {Pi} provider, which targets the upstream @mariozechner/pi-coding-agent
|
|
12
|
+
# CLI.
|
|
13
|
+
class OhMyPi < Base
|
|
14
|
+
CLI_PACKAGE = "@oh-my-pi/pi-coding-agent"
|
|
15
|
+
SUPPORTED_CLI_VERSION = "17.0.1"
|
|
16
|
+
SUPPORTED_CLI_REQUIREMENT = Gem::Requirement.new("= #{SUPPORTED_CLI_VERSION}").freeze
|
|
17
|
+
|
|
18
|
+
# Bun runtime requirements. The omp entrypoint is
|
|
19
|
+
# `#!/usr/bin/env bun` and the published package metadata requires
|
|
20
|
+
# Bun `>= 1.3.14`. Consumers must provision a compatible Bun runtime
|
|
21
|
+
# before installing the @oh-my-pi/pi-coding-agent package.
|
|
22
|
+
#
|
|
23
|
+
# The `bun` npm package relies on its `postinstall` script to fetch the
|
|
24
|
+
# platform binary; installing it with `npm install --ignore-scripts`
|
|
25
|
+
# ships only the Windows shims (`bin/bun.exe`, `bunx.exe`) and leaves
|
|
26
|
+
# Linux/macOS without a working `bun` binary. Provision Bun via the
|
|
27
|
+
# official installer script instead, which fetches the correct
|
|
28
|
+
# platform binary directly.
|
|
29
|
+
BUN_BINARY = "bun"
|
|
30
|
+
BUN_INSTALL_SCRIPT_URL = "https://bun.sh/install"
|
|
31
|
+
SUPPORTED_BUN_VERSION = "1.3.14"
|
|
32
|
+
BUN_REQUIREMENT_STRING = ">= #{SUPPORTED_BUN_VERSION}".freeze
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
def provider_name
|
|
36
|
+
:omp
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def binary_name
|
|
40
|
+
"omp"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def available?
|
|
44
|
+
executor = AgentHarness.configuration.command_executor
|
|
45
|
+
!!executor.which(binary_name)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def provider_metadata_overrides
|
|
49
|
+
{
|
|
50
|
+
auth: {
|
|
51
|
+
service: :omp,
|
|
52
|
+
api_family: :multi_provider
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def firewall_requirements
|
|
58
|
+
{
|
|
59
|
+
domains: [
|
|
60
|
+
"pi.dev"
|
|
61
|
+
],
|
|
62
|
+
ip_ranges: []
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def instruction_file_paths
|
|
67
|
+
[
|
|
68
|
+
{
|
|
69
|
+
path: "AGENTS.md",
|
|
70
|
+
description: "Oh My Pi agent instructions",
|
|
71
|
+
symlink: false
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
path: "SYSTEM.md",
|
|
75
|
+
description: "Oh My Pi system prompt override",
|
|
76
|
+
symlink: false
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def discover_models
|
|
82
|
+
return [] unless available?
|
|
83
|
+
[]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def bun_runtime_contract
|
|
87
|
+
# The official Bun installer reads `BUN_VERSION` to pin the release
|
|
88
|
+
# it downloads, and fetches the platform-appropriate binary itself.
|
|
89
|
+
install_command_prefix = ["sh", "-c"].freeze
|
|
90
|
+
inner_script = "curl -fsSL #{BUN_INSTALL_SCRIPT_URL} | " \
|
|
91
|
+
"BUN_VERSION=#{SUPPORTED_BUN_VERSION} bash"
|
|
92
|
+
install_command = (install_command_prefix + [inner_script]).freeze
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
name: :bun,
|
|
96
|
+
binary_name: BUN_BINARY,
|
|
97
|
+
pinned_version: SUPPORTED_BUN_VERSION,
|
|
98
|
+
version_requirement: BUN_REQUIREMENT_STRING,
|
|
99
|
+
source: :script,
|
|
100
|
+
install_script_url: BUN_INSTALL_SCRIPT_URL,
|
|
101
|
+
install_command_prefix: install_command_prefix,
|
|
102
|
+
install_command: install_command,
|
|
103
|
+
install_command_string: inner_script,
|
|
104
|
+
rationale: "omp entrypoint is #!/usr/bin/env bun; the bun npm package relies on " \
|
|
105
|
+
"its postinstall script to fetch the platform binary, so install Bun " \
|
|
106
|
+
"via the official installer script rather than npm --ignore-scripts"
|
|
107
|
+
}.freeze
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def installation_contract(version: SUPPORTED_CLI_VERSION)
|
|
111
|
+
version = version.strip if version.respond_to?(:strip)
|
|
112
|
+
|
|
113
|
+
unless version.is_a?(String) && !version.empty?
|
|
114
|
+
raise ArgumentError,
|
|
115
|
+
"Unsupported Oh My Pi CLI version #{version.inspect}; " \
|
|
116
|
+
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
parsed_version = begin
|
|
120
|
+
Gem::Version.new(version)
|
|
121
|
+
rescue ArgumentError
|
|
122
|
+
raise ArgumentError,
|
|
123
|
+
"Unsupported Oh My Pi CLI version #{version.inspect}; " \
|
|
124
|
+
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
unless SUPPORTED_CLI_REQUIREMENT.satisfied_by?(parsed_version)
|
|
128
|
+
raise ArgumentError,
|
|
129
|
+
"Unsupported Oh My Pi CLI version #{version.inspect}; " \
|
|
130
|
+
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
package = "#{CLI_PACKAGE}@#{version}".freeze
|
|
134
|
+
install_command_prefix = ["npm", "install", "-g", "--ignore-scripts"].freeze
|
|
135
|
+
install_command = (install_command_prefix + [package]).freeze
|
|
136
|
+
supported_versions = [version].freeze
|
|
137
|
+
version_requirement = SUPPORTED_CLI_REQUIREMENT.requirements
|
|
138
|
+
.map { |op, ver| "#{op} #{ver}".freeze }
|
|
139
|
+
.freeze
|
|
140
|
+
|
|
141
|
+
contract = {
|
|
142
|
+
source: :npm,
|
|
143
|
+
package: package,
|
|
144
|
+
package_name: CLI_PACKAGE,
|
|
145
|
+
version: version,
|
|
146
|
+
version_requirement: version_requirement,
|
|
147
|
+
binary_name: binary_name,
|
|
148
|
+
install_command_prefix: install_command_prefix,
|
|
149
|
+
install_command: install_command,
|
|
150
|
+
supported_versions: supported_versions,
|
|
151
|
+
runtime_requirements: [bun_runtime_contract]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
contract.each_value do |value|
|
|
155
|
+
value.freeze if value.is_a?(String)
|
|
156
|
+
end
|
|
157
|
+
contract.freeze
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def smoke_test_contract
|
|
161
|
+
Base::DEFAULT_SMOKE_TEST_CONTRACT
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def name
|
|
166
|
+
"omp"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def display_name
|
|
170
|
+
"Oh My Pi"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def configuration_schema
|
|
174
|
+
{
|
|
175
|
+
fields: [
|
|
176
|
+
{
|
|
177
|
+
name: :model,
|
|
178
|
+
type: :string,
|
|
179
|
+
label: "Model",
|
|
180
|
+
required: false,
|
|
181
|
+
hint: "Oh My Pi model pattern or ID passed to --model"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: :provider,
|
|
185
|
+
type: :string,
|
|
186
|
+
label: "Provider",
|
|
187
|
+
required: false,
|
|
188
|
+
hint: "Oh My Pi provider name passed to --provider"
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
auth_modes: %i[api_key oauth],
|
|
192
|
+
openai_compatible: false
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def capabilities
|
|
197
|
+
{
|
|
198
|
+
streaming: false,
|
|
199
|
+
file_upload: true,
|
|
200
|
+
vision: true,
|
|
201
|
+
tool_use: true,
|
|
202
|
+
# Oh My Pi's non-interactive CLI currently exposes only text print mode.
|
|
203
|
+
# Keep JSON mode disabled until the CLI ships a structured output flag.
|
|
204
|
+
json_mode: false,
|
|
205
|
+
mcp: false,
|
|
206
|
+
dangerous_mode: false
|
|
207
|
+
}
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def error_patterns
|
|
211
|
+
COMMON_ERROR_PATTERNS
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def supports_tool_control?
|
|
215
|
+
true
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def auth_type
|
|
219
|
+
:oauth
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def execution_semantics
|
|
223
|
+
{
|
|
224
|
+
prompt_delivery: :flag,
|
|
225
|
+
output_format: :text,
|
|
226
|
+
sandbox_aware: false,
|
|
227
|
+
uses_subcommand: false,
|
|
228
|
+
non_interactive_flag: "-p",
|
|
229
|
+
legitimate_exit_codes: [0],
|
|
230
|
+
stderr_is_diagnostic: true,
|
|
231
|
+
parses_rate_limit_reset: false
|
|
232
|
+
}
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
protected
|
|
236
|
+
|
|
237
|
+
def build_command(prompt, options)
|
|
238
|
+
runtime = options[:provider_runtime]
|
|
239
|
+
provider = runtime&.api_provider || @config.provider
|
|
240
|
+
model = runtime&.model || @config.model
|
|
241
|
+
|
|
242
|
+
cmd = [self.class.binary_name, "--no-session"]
|
|
243
|
+
cmd += @config.default_flags if @config.default_flags&.any?
|
|
244
|
+
cmd += runtime.flags if runtime
|
|
245
|
+
cmd += ["--provider", provider] if provider
|
|
246
|
+
cmd += ["--model", model] if model
|
|
247
|
+
cmd << "--no-tools" if options[:tools] == :none
|
|
248
|
+
cmd += ["-p", prompt]
|
|
249
|
+
|
|
250
|
+
cmd
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def default_timeout
|
|
254
|
+
300
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
@@ -28,6 +28,7 @@ module AgentHarness
|
|
|
28
28
|
aliases: [:copilot]
|
|
29
29
|
},
|
|
30
30
|
{name: :pi, require_path: "agent_harness/providers/pi", class_name: :Pi, aliases: []},
|
|
31
|
+
{name: :omp, require_path: "agent_harness/providers/omp", class_name: :OhMyPi, aliases: []},
|
|
31
32
|
{name: :codex, require_path: "agent_harness/providers/codex", class_name: :Codex, aliases: []},
|
|
32
33
|
{name: :opencode, require_path: "agent_harness/providers/opencode", class_name: :Opencode, aliases: []},
|
|
33
34
|
{name: :kilocode, require_path: "agent_harness/providers/kilocode", class_name: :Kilocode, aliases: []},
|
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.
|
|
4
|
+
version: 0.29.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bart Agapinan
|
|
@@ -134,6 +134,7 @@ files:
|
|
|
134
134
|
- lib/agent_harness/providers/kilocode.rb
|
|
135
135
|
- lib/agent_harness/providers/mcp_config_file_support.rb
|
|
136
136
|
- lib/agent_harness/providers/mistral_vibe.rb
|
|
137
|
+
- lib/agent_harness/providers/omp.rb
|
|
137
138
|
- lib/agent_harness/providers/opencode.rb
|
|
138
139
|
- lib/agent_harness/providers/pi.rb
|
|
139
140
|
- lib/agent_harness/providers/rate_limit_reset_parsing.rb
|