agent-harness 0.17.1 → 0.17.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68dcb8a37c35c6ded8f4d01e0f0d6d14e1588ac41a88f2b613fa2fbab9decc79
4
- data.tar.gz: 6317ce0f0ef2aaadf13dfa39b6897d67a4b453bbb7b7b09db348366d33b8d993
3
+ metadata.gz: f15e1c2315d17c1250d547c58c3f04b2d9daf491a3b56fcb23e58b824a68e6a1
4
+ data.tar.gz: 42bd06fe77703e25a8c61ec8667de90d1b3e84e483d1651b36c0e0013cdc610a
5
5
  SHA512:
6
- metadata.gz: 59989e7197cf596e87ae4ffb87f252419d72f3c9c119de4f210fc4a41c7b3777187a69f1745e65f7a1cefb0010b87f316cd375c673de56e906e4b223364dac44
7
- data.tar.gz: a691c948ee1fb9f3f6777b3624863b42024ecf85c84b5b320a0e258cf4f7467db7832e703048cee42272cd3e946c020321873788d48de225acaf00eaa8bee757
6
+ metadata.gz: ba35bf2c9b5e6de554664cfbf1dacbb70d0d9ceb017c6fc1be8bb8c63b73ab295b1d01e28de3650ac74d08d4908650dd11dfc267099482e2ea933a24ae33d5e9
7
+ data.tar.gz: c8fcf04ce5c2ab05c2007fc6642457adbe2bafb48cc11e0290b97834d269c4fbe8be411d9227aceef8315975d97a9ff38d8beae270d34365f07f2e851c883ddb
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.17.1"
2
+ ".": "0.17.2"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.17.2](https://github.com/viamin/agent-harness/compare/agent-harness/v0.17.1...agent-harness/v0.17.2) (2026-05-05)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * 160: Add support for the pi agent CLI ([#203](https://github.com/viamin/agent-harness/issues/203)) ([0aeb607](https://github.com/viamin/agent-harness/commit/0aeb607ea98b52ba8202726dc946b8c1db09a3cd))
9
+
3
10
  ## [0.17.1](https://github.com/viamin/agent-harness/compare/agent-harness/v0.17.0...agent-harness/v0.17.1) (2026-05-05)
4
11
 
5
12
 
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
- - **9 Built-in Providers**: Claude Code, Cursor, Gemini CLI, GitHub Copilot, Codex, Aider, OpenCode, Kilocode, Mistral Vibe
8
+ - **10 Built-in Providers**: Claude Code, Cursor, Gemini CLI, GitHub Copilot, Codex, 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
@@ -104,6 +104,7 @@ end
104
104
  | `:gemini` | `gemini` | Google Gemini CLI |
105
105
  | `:github_copilot` | `copilot` | GitHub Copilot CLI |
106
106
  | `:codex` | `codex` | OpenAI Codex CLI |
107
+ | `:pi` | `pi` | Pi coding agent CLI |
107
108
  | `:aider` | `aider` | Aider coding assistant |
108
109
  | `:opencode` | `opencode` | OpenCode CLI |
109
110
  | `:kilocode` | `kilo` | Kilocode CLI |
@@ -187,7 +188,7 @@ puts contract[:supported_versions][:requirement]
187
188
 
188
189
  # List all registered providers
189
190
  AgentHarness::Providers::Registry.instance.all
190
- # => [:claude, :cursor, :gemini, :github_copilot, :codex, :opencode, :kilocode, :aider, :mistral_vibe]
191
+ # => [:claude, :cursor, :gemini, :github_copilot, :pi, :codex, :opencode, :kilocode, :aider, :mistral_vibe]
191
192
  ```
192
193
 
193
194
  For Claude, the install contract is the first-class source of truth for:
@@ -377,7 +377,7 @@ module AgentHarness
377
377
  # Provider-specific configuration
378
378
  class ProviderConfig
379
379
  attr_accessor :enabled, :type, :priority, :models, :default_flags, :timeout, :model,
380
- :externally_sandboxed
380
+ :provider, :externally_sandboxed
381
381
 
382
382
  attr_reader :name
383
383
 
@@ -390,6 +390,7 @@ module AgentHarness
390
390
  @default_flags = []
391
391
  @timeout = nil
392
392
  @model = nil
393
+ @provider = nil
393
394
  @externally_sandboxed = false
394
395
  end
395
396
 
@@ -46,9 +46,9 @@ module AgentHarness
46
46
  validate_optional_string!(:base_url, base_url)
47
47
  validate_optional_string!(:api_provider, api_provider)
48
48
 
49
- @model = model
50
- @base_url = base_url
51
- @api_provider = api_provider
49
+ @model = normalize_optional_string(model)
50
+ @base_url = normalize_optional_string(base_url)
51
+ @api_provider = normalize_optional_string(api_provider)
52
52
 
53
53
  env_hash = env.nil? ? {} : env
54
54
  unless env_hash.is_a?(Hash)
@@ -113,9 +113,9 @@ module AgentHarness
113
113
  "chat_tools must be an Array of Hashes; invalid element at index #{index}: #{tool.inspect} (#{tool.class})"
114
114
  end
115
115
  end
116
- @chat_base_url = chat_base_url
117
- @chat_model = chat_model
118
- @chat_api_key = chat_api_key
116
+ @chat_base_url = normalize_optional_string(chat_base_url)
117
+ @chat_model = normalize_optional_string(chat_model)
118
+ @chat_api_key = normalize_optional_string(chat_api_key)
119
119
  @chat_max_tokens = chat_max_tokens
120
120
  @chat_tools = normalized_chat_tools&.freeze
121
121
 
@@ -191,5 +191,12 @@ module AgentHarness
191
191
 
192
192
  raise ArgumentError, "#{name} must be a String or nil (got #{value.class})"
193
193
  end
194
+
195
+ def normalize_optional_string(value)
196
+ return value unless value.respond_to?(:strip)
197
+
198
+ normalized = value.strip
199
+ normalized.empty? ? nil : normalized
200
+ end
194
201
  end
195
202
  end
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems/requirement"
4
+
5
+ module AgentHarness
6
+ module Providers
7
+ # Pi coding agent CLI provider
8
+ #
9
+ # Provides integration with the Pi terminal coding agent.
10
+ class Pi < Base
11
+ CLI_PACKAGE = "@mariozechner/pi-coding-agent"
12
+ SUPPORTED_CLI_VERSION = "0.73.0"
13
+ SUPPORTED_CLI_REQUIREMENT = Gem::Requirement.new("= #{SUPPORTED_CLI_VERSION}").freeze
14
+
15
+ class << self
16
+ def provider_name
17
+ :pi
18
+ end
19
+
20
+ def binary_name
21
+ "pi"
22
+ end
23
+
24
+ def available?
25
+ executor = AgentHarness.configuration.command_executor
26
+ !!executor.which(binary_name)
27
+ end
28
+
29
+ def provider_metadata_overrides
30
+ {
31
+ auth: {
32
+ service: :pi,
33
+ api_family: :multi_provider
34
+ }
35
+ }
36
+ end
37
+
38
+ def firewall_requirements
39
+ {
40
+ domains: [
41
+ "pi.dev"
42
+ ],
43
+ ip_ranges: []
44
+ }
45
+ end
46
+
47
+ def instruction_file_paths
48
+ [
49
+ {
50
+ path: "AGENTS.md",
51
+ description: "Pi agent instructions",
52
+ symlink: false
53
+ },
54
+ {
55
+ path: "SYSTEM.md",
56
+ description: "Pi system prompt override",
57
+ symlink: false
58
+ }
59
+ ]
60
+ end
61
+
62
+ def discover_models
63
+ return [] unless available?
64
+ []
65
+ end
66
+
67
+ def installation_contract(version: SUPPORTED_CLI_VERSION)
68
+ version = version.strip if version.respond_to?(:strip)
69
+
70
+ unless version.is_a?(String) && !version.empty?
71
+ raise ArgumentError,
72
+ "Unsupported Pi CLI version #{version.inspect}; " \
73
+ "supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
74
+ end
75
+
76
+ parsed_version = begin
77
+ Gem::Version.new(version)
78
+ rescue ArgumentError
79
+ raise ArgumentError,
80
+ "Unsupported Pi CLI version #{version.inspect}; " \
81
+ "supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
82
+ end
83
+
84
+ unless SUPPORTED_CLI_REQUIREMENT.satisfied_by?(parsed_version)
85
+ raise ArgumentError,
86
+ "Unsupported Pi CLI version #{version.inspect}; " \
87
+ "supported versions must satisfy #{SUPPORTED_CLI_REQUIREMENT}"
88
+ end
89
+
90
+ package = "#{CLI_PACKAGE}@#{version}".freeze
91
+ install_command_prefix = ["npm", "install", "-g", "--ignore-scripts"].freeze
92
+ install_command = (install_command_prefix + [package]).freeze
93
+ supported_versions = [version].freeze
94
+ version_requirement = SUPPORTED_CLI_REQUIREMENT.requirements
95
+ .map { |op, ver| "#{op} #{ver}".freeze }
96
+ .freeze
97
+
98
+ contract = {
99
+ source: :npm,
100
+ package: package,
101
+ package_name: CLI_PACKAGE,
102
+ version: version,
103
+ version_requirement: version_requirement,
104
+ binary_name: binary_name,
105
+ install_command_prefix: install_command_prefix,
106
+ install_command: install_command,
107
+ supported_versions: supported_versions
108
+ }
109
+
110
+ contract.each_value do |value|
111
+ value.freeze if value.is_a?(String)
112
+ end
113
+ contract.freeze
114
+ end
115
+
116
+ def smoke_test_contract
117
+ Base::DEFAULT_SMOKE_TEST_CONTRACT
118
+ end
119
+ end
120
+
121
+ def name
122
+ "pi"
123
+ end
124
+
125
+ def display_name
126
+ "Pi Coding Agent"
127
+ end
128
+
129
+ def configuration_schema
130
+ {
131
+ fields: [
132
+ {
133
+ name: :model,
134
+ type: :string,
135
+ label: "Model",
136
+ required: false,
137
+ hint: "Pi model pattern or ID passed to --model"
138
+ },
139
+ {
140
+ name: :provider,
141
+ type: :string,
142
+ label: "Provider",
143
+ required: false,
144
+ hint: "Pi provider name passed to --provider"
145
+ }
146
+ ],
147
+ auth_modes: %i[api_key oauth],
148
+ openai_compatible: false
149
+ }
150
+ end
151
+
152
+ def capabilities
153
+ {
154
+ streaming: false,
155
+ file_upload: true,
156
+ vision: true,
157
+ tool_use: true,
158
+ # Pi's non-interactive CLI currently exposes only text print mode.
159
+ # Keep JSON mode disabled until the CLI ships a structured output flag.
160
+ json_mode: false,
161
+ mcp: false,
162
+ dangerous_mode: false
163
+ }
164
+ end
165
+
166
+ def error_patterns
167
+ COMMON_ERROR_PATTERNS
168
+ end
169
+
170
+ def supports_tool_control?
171
+ true
172
+ end
173
+
174
+ def auth_type
175
+ :oauth
176
+ end
177
+
178
+ def execution_semantics
179
+ {
180
+ prompt_delivery: :flag,
181
+ output_format: :text,
182
+ sandbox_aware: false,
183
+ uses_subcommand: false,
184
+ non_interactive_flag: "-p",
185
+ legitimate_exit_codes: [0],
186
+ stderr_is_diagnostic: true,
187
+ parses_rate_limit_reset: false
188
+ }
189
+ end
190
+
191
+ protected
192
+
193
+ def build_command(prompt, options)
194
+ runtime = options[:provider_runtime]
195
+ provider = runtime&.api_provider || @config.provider
196
+ model = runtime&.model || @config.model
197
+
198
+ cmd = [self.class.binary_name, "--no-session"]
199
+ cmd += @config.default_flags if @config.default_flags&.any?
200
+ cmd += runtime.flags if runtime
201
+ cmd += ["--provider", provider] if provider
202
+ cmd += ["--model", model] if model
203
+ cmd << "--no-tools" if options[:tools] == :none
204
+ cmd += ["-p", prompt]
205
+
206
+ cmd
207
+ end
208
+
209
+ def default_timeout
210
+ 300
211
+ end
212
+ end
213
+ end
214
+ end
@@ -27,6 +27,7 @@ module AgentHarness
27
27
  class_name: :GithubCopilot,
28
28
  aliases: [:copilot]
29
29
  },
30
+ {name: :pi, require_path: "agent_harness/providers/pi", class_name: :Pi, aliases: []},
30
31
  {name: :codex, require_path: "agent_harness/providers/codex", class_name: :Codex, aliases: []},
31
32
  {name: :opencode, require_path: "agent_harness/providers/opencode", class_name: :Opencode, aliases: []},
32
33
  {name: :kilocode, require_path: "agent_harness/providers/kilocode", class_name: :Kilocode, aliases: []},
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AgentHarness
4
- VERSION = "0.17.1"
4
+ VERSION = "0.17.2"
5
5
  end
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.17.1
4
+ version: 0.17.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan
@@ -133,6 +133,7 @@ files:
133
133
  - lib/agent_harness/providers/mcp_config_file_support.rb
134
134
  - lib/agent_harness/providers/mistral_vibe.rb
135
135
  - lib/agent_harness/providers/opencode.rb
136
+ - lib/agent_harness/providers/pi.rb
136
137
  - lib/agent_harness/providers/rate_limit_reset_parsing.rb
137
138
  - lib/agent_harness/providers/registry.rb
138
139
  - lib/agent_harness/providers/token_usage_parsing.rb