agent-harness 0.28.6 → 0.30.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 +14 -0
- data/README.md +3 -2
- data/lib/agent_harness/providers/adapter.rb +26 -0
- data/lib/agent_harness/providers/omp.rb +415 -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: ae26a14b4826681fbe36862291bfc6342afab2dd709e2386312ab072582fc3cb
|
|
4
|
+
data.tar.gz: b53f1b28bd4662e1522db2670e7f698e71937b600d80d1fa30de056a3e3d9deb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e16d723dd4945831271b4d10cfc1de69042181796fecde7500c840060fe18f4ba637d567cec2ca342c015b3dde4d288e4716ae0471cded2cd43572f13bb7208f
|
|
7
|
+
data.tar.gz: 751f5a515d4ff3764a24fd21dba24733545d4da3fe06803d02d2d270504a7fcce12ea2a9dc1a08ede985aca4bd0e76c07e75fcb5be6e03ac7067e6ee005c38ae
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@
|
|
|
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.30.0](https://github.com/viamin/agent-harness/compare/agent-harness/v0.29.0...agent-harness/v0.30.0) (2026-07-16)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* Implement Oh My Pi execution, auth, and smoke-test contract ([#300](https://github.com/viamin/agent-harness/issues/300)) ([dc3f5ec](https://github.com/viamin/agent-harness/commit/dc3f5ecce4508f103a8b769727e31aaa3d694c7e))
|
|
15
|
+
|
|
16
|
+
## [0.29.0](https://github.com/viamin/agent-harness/compare/agent-harness/v0.28.6...agent-harness/v0.29.0) (2026-07-16)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* 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))
|
|
22
|
+
|
|
9
23
|
## [0.28.6](https://github.com/viamin/agent-harness/compare/agent-harness/v0.28.5...agent-harness/v0.28.6) (2026-07-12)
|
|
10
24
|
|
|
11
25
|
|
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,415 @@
|
|
|
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
|
+
# Smoke-test contract suitable for container health probes.
|
|
35
|
+
#
|
|
36
|
+
# Oh My Pi runs non-interactively via `-p` with `--no-session`, so the
|
|
37
|
+
# probe is a single deterministic round trip. The contract is tuned for
|
|
38
|
+
# a tight probe budget: a short prompt that elicits a predictable reply
|
|
39
|
+
# and a bounded timeout that accommodates a cold Bun runtime start.
|
|
40
|
+
SMOKE_TEST_CONTRACT = {
|
|
41
|
+
prompt: "Reply with exactly OK.",
|
|
42
|
+
expected_output: "OK",
|
|
43
|
+
timeout: 30,
|
|
44
|
+
require_output: true,
|
|
45
|
+
success_message: "Oh My Pi smoke test passed"
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
# Oh My Pi is a multi-provider CLI: it routes to a backend through
|
|
49
|
+
# `--provider` and surfaces that backend's error vocabulary. The
|
|
50
|
+
# patterns below extend the shared HTTP-style set with Oh My Pi /
|
|
51
|
+
# upstream-Pi specific phrasing observed for auth expiry, quota and
|
|
52
|
+
# rate-limit surfaces, model resolution failures, and transient
|
|
53
|
+
# network faults.
|
|
54
|
+
ERROR_PATTERNS = {
|
|
55
|
+
rate_limited: COMMON_ERROR_PATTERNS[:rate_limited],
|
|
56
|
+
auth_expired: COMMON_ERROR_PATTERNS[:auth_expired] + [
|
|
57
|
+
/\b401\b/,
|
|
58
|
+
/session.*(?:expired|invalid)/i,
|
|
59
|
+
/log(?:ged)?.?in.*required/i,
|
|
60
|
+
/api.?key.*(?:invalid|missing|expired|revoked)/i,
|
|
61
|
+
/token.*(?:expired|invalid|revoked)/i,
|
|
62
|
+
/credentials.*(?:expired|invalid|missing)/i
|
|
63
|
+
],
|
|
64
|
+
quota_exceeded: COMMON_ERROR_PATTERNS[:quota_exceeded],
|
|
65
|
+
# Model resolution is surfaced by both the omp CLI (unknown
|
|
66
|
+
# `--model`/`--provider` combination) and the upstream backend.
|
|
67
|
+
# Treat it as a distinct, non-retryable category so callers can
|
|
68
|
+
# fall back to a known-good model instead of retrying the same id.
|
|
69
|
+
model_not_found: [
|
|
70
|
+
/model.*not.*found/i,
|
|
71
|
+
/no.*such.*model/i,
|
|
72
|
+
/unknown.*model/i,
|
|
73
|
+
/model.*does.*not.*exist/i,
|
|
74
|
+
/invalid.*model/i,
|
|
75
|
+
/model.*unavailable/i,
|
|
76
|
+
/provider.*does.*not.*support.*model/i
|
|
77
|
+
],
|
|
78
|
+
transient: COMMON_ERROR_PATTERNS[:transient] + [
|
|
79
|
+
/connection.*reset/i,
|
|
80
|
+
/econnreset/i,
|
|
81
|
+
/socket.*hang.*up/i,
|
|
82
|
+
/network.*error/i
|
|
83
|
+
]
|
|
84
|
+
}.tap { |patterns| patterns.each_value(&:freeze) }.freeze
|
|
85
|
+
|
|
86
|
+
# Non-actionable output that Oh My Pi writes while the Bun runtime and
|
|
87
|
+
# the agent bootstrap. Downstream consumers use these to filter probe
|
|
88
|
+
# and run output so startup banners do not masquerade as failures.
|
|
89
|
+
NOISY_OUTPUT_PATTERNS = [
|
|
90
|
+
/oh.?my.?pi\b/i,
|
|
91
|
+
/pi(?:-coding.?agent)?\s+v?\d+\.\d+/i,
|
|
92
|
+
/\bbun\s+v?\d+\.\d+/i,
|
|
93
|
+
/\bloading/i,
|
|
94
|
+
/\binitializing/i,
|
|
95
|
+
/\bwarming.?up/i,
|
|
96
|
+
/fetching.*model/i
|
|
97
|
+
].freeze
|
|
98
|
+
|
|
99
|
+
class << self
|
|
100
|
+
def provider_name
|
|
101
|
+
:omp
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def binary_name
|
|
105
|
+
"omp"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def available?
|
|
109
|
+
executor = AgentHarness.configuration.command_executor
|
|
110
|
+
!!executor.which(binary_name)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def provider_metadata_overrides
|
|
114
|
+
{
|
|
115
|
+
auth: {
|
|
116
|
+
service: :omp,
|
|
117
|
+
api_family: :multi_provider,
|
|
118
|
+
# Oh My Pi routes to a backend through `--provider` and reads
|
|
119
|
+
# that backend's API key from its conventional env var. The
|
|
120
|
+
# harness never reuses the upstream Pi credential store
|
|
121
|
+
# (paid_pi_auth_entry); callers pass backend keys per request
|
|
122
|
+
# through ProviderRuntime#env, which build_env materializes into
|
|
123
|
+
# the subprocess environment. This keeps the omp runner an
|
|
124
|
+
# independent entity from :pi.
|
|
125
|
+
#
|
|
126
|
+
# Deliberately do NOT advertise a harness-managed credential
|
|
127
|
+
# store here. Authentication has no omp read/write/validate path,
|
|
128
|
+
# so auth_status(:omp) reports "not implemented"; exposing
|
|
129
|
+
# credential_store would imply a session store that does not
|
|
130
|
+
# exist and let callers infer harness-managed session auth.
|
|
131
|
+
# Surface the implemented per-request env model instead.
|
|
132
|
+
api_key_source: :provider_runtime_env
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def firewall_requirements
|
|
138
|
+
{
|
|
139
|
+
domains: [
|
|
140
|
+
"pi.dev"
|
|
141
|
+
],
|
|
142
|
+
ip_ranges: []
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def instruction_file_paths
|
|
147
|
+
[
|
|
148
|
+
{
|
|
149
|
+
path: "AGENTS.md",
|
|
150
|
+
description: "Oh My Pi agent instructions",
|
|
151
|
+
symlink: false
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
path: "SYSTEM.md",
|
|
155
|
+
description: "Oh My Pi system prompt override",
|
|
156
|
+
symlink: false
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def discover_models
|
|
162
|
+
return [] unless available?
|
|
163
|
+
[]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def bun_runtime_contract
|
|
167
|
+
# The official Bun installer reads `BUN_VERSION` to pin the release
|
|
168
|
+
# it downloads, and fetches the platform-appropriate binary itself.
|
|
169
|
+
install_command_prefix = ["sh", "-c"].freeze
|
|
170
|
+
inner_script = "curl -fsSL #{BUN_INSTALL_SCRIPT_URL} | " \
|
|
171
|
+
"BUN_VERSION=#{SUPPORTED_BUN_VERSION} bash"
|
|
172
|
+
install_command = (install_command_prefix + [inner_script]).freeze
|
|
173
|
+
|
|
174
|
+
{
|
|
175
|
+
name: :bun,
|
|
176
|
+
binary_name: BUN_BINARY,
|
|
177
|
+
pinned_version: SUPPORTED_BUN_VERSION,
|
|
178
|
+
version_requirement: BUN_REQUIREMENT_STRING,
|
|
179
|
+
source: :script,
|
|
180
|
+
install_script_url: BUN_INSTALL_SCRIPT_URL,
|
|
181
|
+
install_command_prefix: install_command_prefix,
|
|
182
|
+
install_command: install_command,
|
|
183
|
+
install_command_string: inner_script,
|
|
184
|
+
rationale: "omp entrypoint is #!/usr/bin/env bun; the bun npm package relies on " \
|
|
185
|
+
"its postinstall script to fetch the platform binary, so install Bun " \
|
|
186
|
+
"via the official installer script rather than npm --ignore-scripts"
|
|
187
|
+
}.freeze
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def installation_contract(version: SUPPORTED_CLI_VERSION)
|
|
191
|
+
version = version.strip if version.respond_to?(:strip)
|
|
192
|
+
|
|
193
|
+
unless version.is_a?(String) && !version.empty?
|
|
194
|
+
raise ArgumentError,
|
|
195
|
+
"Unsupported Oh My Pi CLI version #{version.inspect}; " \
|
|
196
|
+
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
parsed_version = begin
|
|
200
|
+
Gem::Version.new(version)
|
|
201
|
+
rescue ArgumentError
|
|
202
|
+
raise ArgumentError,
|
|
203
|
+
"Unsupported Oh My Pi CLI version #{version.inspect}; " \
|
|
204
|
+
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
unless SUPPORTED_CLI_REQUIREMENT.satisfied_by?(parsed_version)
|
|
208
|
+
raise ArgumentError,
|
|
209
|
+
"Unsupported Oh My Pi CLI version #{version.inspect}; " \
|
|
210
|
+
"supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
package = "#{CLI_PACKAGE}@#{version}".freeze
|
|
214
|
+
install_command_prefix = ["npm", "install", "-g", "--ignore-scripts"].freeze
|
|
215
|
+
install_command = (install_command_prefix + [package]).freeze
|
|
216
|
+
supported_versions = [version].freeze
|
|
217
|
+
version_requirement = SUPPORTED_CLI_REQUIREMENT.requirements
|
|
218
|
+
.map { |op, ver| "#{op} #{ver}".freeze }
|
|
219
|
+
.freeze
|
|
220
|
+
|
|
221
|
+
contract = {
|
|
222
|
+
source: :npm,
|
|
223
|
+
package: package,
|
|
224
|
+
package_name: CLI_PACKAGE,
|
|
225
|
+
version: version,
|
|
226
|
+
version_requirement: version_requirement,
|
|
227
|
+
binary_name: binary_name,
|
|
228
|
+
install_command_prefix: install_command_prefix,
|
|
229
|
+
install_command: install_command,
|
|
230
|
+
supported_versions: supported_versions,
|
|
231
|
+
runtime_requirements: [bun_runtime_contract]
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
contract.each_value do |value|
|
|
235
|
+
value.freeze if value.is_a?(String)
|
|
236
|
+
end
|
|
237
|
+
contract.freeze
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def smoke_test_contract
|
|
241
|
+
SMOKE_TEST_CONTRACT
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def name
|
|
246
|
+
"omp"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def display_name
|
|
250
|
+
"Oh My Pi"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def configuration_schema
|
|
254
|
+
{
|
|
255
|
+
fields: [
|
|
256
|
+
{
|
|
257
|
+
name: :model,
|
|
258
|
+
type: :string,
|
|
259
|
+
label: "Model",
|
|
260
|
+
required: false,
|
|
261
|
+
hint: "Oh My Pi model pattern or ID passed to --model"
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: :provider,
|
|
265
|
+
type: :string,
|
|
266
|
+
label: "Provider",
|
|
267
|
+
required: false,
|
|
268
|
+
hint: "Oh My Pi provider name passed to --provider"
|
|
269
|
+
}
|
|
270
|
+
],
|
|
271
|
+
auth_modes: %i[api_key oauth],
|
|
272
|
+
openai_compatible: false
|
|
273
|
+
}
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def capabilities
|
|
277
|
+
{
|
|
278
|
+
streaming: false,
|
|
279
|
+
file_upload: true,
|
|
280
|
+
vision: true,
|
|
281
|
+
tool_use: true,
|
|
282
|
+
# Oh My Pi's non-interactive CLI currently exposes only text print mode.
|
|
283
|
+
# Keep JSON mode disabled until the CLI ships a structured output flag.
|
|
284
|
+
json_mode: false,
|
|
285
|
+
# MCP capability decision: the omp non-interactive print-mode path
|
|
286
|
+
# (`omp --no-session -p`) does not accept an MCP server config flag
|
|
287
|
+
# in the harness's supported CLI version. Keep MCP disabled here so
|
|
288
|
+
# callers do not attempt to attach servers that the runtime would
|
|
289
|
+
# reject. Revisit once omp ships a stable `--mcp-config` flag.
|
|
290
|
+
mcp: false,
|
|
291
|
+
dangerous_mode: false
|
|
292
|
+
}
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def error_patterns
|
|
296
|
+
ERROR_PATTERNS
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Downstream-facing error classification. Augments the shared quota set
|
|
300
|
+
# with auth-expiry, model-resolution, and authentication-specific
|
|
301
|
+
# phrasing so consumers can route omp failures without re-deriving the
|
|
302
|
+
# CLI vocabulary. The inherited `:quota` set is deliberately preserved
|
|
303
|
+
# (not overridden) so omp's multi-provider backends surface their full
|
|
304
|
+
# credit/balance vocabulary (requires more credits, insufficient
|
|
305
|
+
# balance, spend limit reached, billing limit, etc.).
|
|
306
|
+
def error_classification_patterns
|
|
307
|
+
super.merge(
|
|
308
|
+
auth_expired: ERROR_PATTERNS[:auth_expired],
|
|
309
|
+
authentication: [
|
|
310
|
+
/api.?key.*not.*(?:set|configured)/i,
|
|
311
|
+
/no.*api.?key/i,
|
|
312
|
+
/missing.*credentials/i,
|
|
313
|
+
/log(?:ged)?.?in.*required/i
|
|
314
|
+
],
|
|
315
|
+
model_not_found: ERROR_PATTERNS[:model_not_found]
|
|
316
|
+
)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Oh My Pi emits a startup banner (version, Bun runtime, model fetch)
|
|
320
|
+
# that is not actionable. Expose it so callers can strip probe noise.
|
|
321
|
+
def noisy_error_patterns
|
|
322
|
+
NOISY_OUTPUT_PATTERNS
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def translate_error(message)
|
|
326
|
+
if ERROR_PATTERNS[:model_not_found].any? { |p| message.match?(p) }
|
|
327
|
+
"Oh My Pi could not resolve the requested model. Check --model/--provider."
|
|
328
|
+
elsif /api.?key.*not.*(?:set|configured)/i.match?(message) || /no.*api.?key/i.match?(message)
|
|
329
|
+
"Oh My Pi API key not set for the selected provider."
|
|
330
|
+
else
|
|
331
|
+
message
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Oh My Pi runs stateless through `--no-session`; the harness does not
|
|
336
|
+
# expose session persistence, so callers cannot resume a session.
|
|
337
|
+
def supports_sessions?
|
|
338
|
+
false
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def supports_tool_control?
|
|
342
|
+
true
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def auth_type
|
|
346
|
+
:oauth
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Conventional backend API-key env vars the omp CLI reads. Oh My Pi is
|
|
350
|
+
# multi-provider: the effective var is the one matching the selected
|
|
351
|
+
# `--provider`, supplied per request through ProviderRuntime#env.
|
|
352
|
+
def api_key_env_var_names
|
|
353
|
+
[
|
|
354
|
+
"ANTHROPIC_API_KEY",
|
|
355
|
+
"OPENAI_API_KEY",
|
|
356
|
+
"GEMINI_API_KEY",
|
|
357
|
+
"GOOGLE_API_KEY",
|
|
358
|
+
"XAI_API_KEY",
|
|
359
|
+
"DEEPSEEK_API_KEY",
|
|
360
|
+
"OPENROUTER_API_KEY",
|
|
361
|
+
"GROQ_API_KEY",
|
|
362
|
+
"MISTRAL_API_KEY"
|
|
363
|
+
]
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# omp routes through `--provider` rather than an env-driven proxy/base
|
|
367
|
+
# URL, so there are no known proxy header vars to scrub when a caller
|
|
368
|
+
# supplies its own key.
|
|
369
|
+
def api_key_unset_vars
|
|
370
|
+
[]
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# When running against an OAuth/subscription session, drop backend
|
|
374
|
+
# API-key env vars so the CLI prefers the stored session credentials.
|
|
375
|
+
def subscription_unset_vars
|
|
376
|
+
api_key_env_var_names
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def execution_semantics
|
|
380
|
+
{
|
|
381
|
+
prompt_delivery: :flag,
|
|
382
|
+
output_format: :text,
|
|
383
|
+
sandbox_aware: false,
|
|
384
|
+
uses_subcommand: false,
|
|
385
|
+
non_interactive_flag: "-p",
|
|
386
|
+
legitimate_exit_codes: [0],
|
|
387
|
+
stderr_is_diagnostic: true,
|
|
388
|
+
parses_rate_limit_reset: false
|
|
389
|
+
}
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
protected
|
|
393
|
+
|
|
394
|
+
def build_command(prompt, options)
|
|
395
|
+
runtime = options[:provider_runtime]
|
|
396
|
+
provider = runtime&.api_provider || @config.provider
|
|
397
|
+
model = runtime&.model || @config.model
|
|
398
|
+
|
|
399
|
+
cmd = [self.class.binary_name, "--no-session"]
|
|
400
|
+
cmd += @config.default_flags if @config.default_flags&.any?
|
|
401
|
+
cmd += runtime.flags if runtime
|
|
402
|
+
cmd += ["--provider", provider] if provider
|
|
403
|
+
cmd += ["--model", model] if model
|
|
404
|
+
cmd << "--no-tools" if options[:tools] == :none
|
|
405
|
+
cmd += ["-p", prompt]
|
|
406
|
+
|
|
407
|
+
cmd
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def default_timeout
|
|
411
|
+
300
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
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.30.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
|