agent-cli-runtime 0.1.1 → 0.2.4

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: 59315cc0411f928db6beb1ecea2a7f96986c26e7436ea60d14d105c7b0252c71
4
- data.tar.gz: 073dfc6f68dabb86a4307a6dbf0a61a3238f6dea32256ac04432d174ea8acf6f
3
+ metadata.gz: e0202bb311856ad96fcd486517d11cfc7175db0102a188920c4d851bf338b7d6
4
+ data.tar.gz: 2e72a23b5e7a021fc439a9ce8ef34bf166c0af0141de491a27686d6f3ba3f139
5
5
  SHA512:
6
- metadata.gz: abca7ef13599eae3e161b085ee97e334a3a665fd9a8de01ae0bbb3719cb9034edda857e6ad12c188936cd3b2e696811488b28505c6aa8edf6cff25aabda02959
7
- data.tar.gz: e8d831b5884967165f4422472b6bce5510f76fd581173955536eadd871e851358f3017a851865cc64530b0ffa42b47c8bfe5549d709978e8ef6a54b270a2f30b
6
+ metadata.gz: b3736f38aa29d809028b1e99c9df1dd17ddb4a210382e89f752def380e84cf22e3f144da2d10ee6c86c2dab8496865a35347196277367a0734b9c39991c33f0e
7
+ data.tar.gz: 5adacccc362958a8cc9a2eeab22b9100041101de4795d7f31ddaa563406ec031f6b3662e84eca058b144ab86ee41d1505c42baa267c1eae436cda4566680b100
data/CHANGELOG.md CHANGED
@@ -1,5 +1,92 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.2.4 - 2026-08-25
6
+
7
+ - Expose the OpenCode permission compiler independently from the prepared
8
+ overlay so callers can supply the same deny-first rules through native
9
+ per-process configuration without duplicating policy generation.
10
+
11
+ - Carry the already-resolved OpenCode executable on `ProbeRequest` (`executable:`)
12
+ instead of re-encoding it as the `AGENT_CLI_RUNTIME_OPENCODE_BIN` environment
13
+ override. The route-aware probe now resolves the executable once — honoring a
14
+ caller-supplied value first — and threads that single value through the
15
+ installation check, version check, every local inspection command, and the
16
+ reported result executable. `Profile#binary_installed?`,
17
+ `Profile#check_version!`, and `Profile#capture_local` accept an optional
18
+ `executable:` override; existing environment-based resolution is unchanged
19
+ when no explicit value is carried.
20
+
21
+ ## 0.2.3 - 2026-08-21
22
+
23
+ - Classify extracted provider failures once at the runtime boundary as
24
+ `provider_limit`, `rate_limited`, `model_output_limit`, or `provider_error`.
25
+ HTTP 402/429 payloads are authoritative even when their provider-specific
26
+ wording does not match an orchestrator's legacy quota phrases.
27
+
28
+ ## 0.2.2 - 2026-08-18
29
+
30
+ - Deliver built-in Pi prompts through its native non-TTY stdin reader instead
31
+ of one positional argv element. This prevents implementation-sized prompts
32
+ from failing at process spawn with the operating system's per-argument
33
+ `E2BIG` limit; Codex retains its separate stdin-plus-`-` transport.
34
+ - Treat Pi's `stopReason: "length"` as a typed `model_output_limit` failure.
35
+ Pi exits zero after this provider stop even when the model exhausted its
36
+ output allowance before writing the requested artifact; callers can now
37
+ distinguish that incomplete turn from an agent that silently produced
38
+ 0 bytes and tell operators to raise `maxTokens` or lower reasoning effort.
39
+ - Declare Grok's filesystem sandbox flags, so a caller asking for confined
40
+ execution gets it instead of being told Grok cannot confine. `--sandbox
41
+ workspace` limits writes to the working directory and `--sandbox read-only`
42
+ forbids them, both built-in profiles that custom ones extend from
43
+ `~/.grok/sandbox.toml`; `--always-approve` suppresses the approval prompt a
44
+ headless run can never answer, leaving the sandbox — not the prompt — as the
45
+ boundary. Only the Grok profile changes.
46
+
47
+ ## 0.2.1 - 2026-08-17
48
+
49
+ - Add per-profile provider-error extraction so a refusal a CLI reports on its
50
+ event stream is distinguishable from an agent that genuinely produced
51
+ nothing. `extract_provider_error` returns the provider, the HTTP status when
52
+ the provider supplied one, and the redacted provider text.
53
+ - Add the `pi` extractor for turns that keep the envelope type and carry the
54
+ refusal in `stopReason`/`errorMessage`, which no event-type match observes.
55
+ Such turns previously reached the caller as a clean run with empty content
56
+ while the process exited zero.
57
+ - Default every other profile to the previously assumed shapes — dedicated
58
+ `error`, `turn.failed`, and `rate_limit_event` events plus failed `result`
59
+ events — so no existing profile changes behaviour.
60
+ - Read usage from the assistant message and accept the bare
61
+ `input`/`output`/`cacheRead`/`cacheWrite` spellings, so a provider reporting
62
+ usage there is metered instead of silently recording nothing. The bare
63
+ spellings are matched last, leaving a provider that reports explicit
64
+ `*_tokens` keys with its existing reading.
65
+
66
+ ## 0.2.0 - 2026-08-15
67
+
68
+ - Add first-class OpenCode `1.18.16+` support alongside the built-in Claude
69
+ Code, Codex CLI, Pi, and Grok CLI profiles. OpenCode uses the shared request
70
+ and facade, then adds provider-specific preparation, route probing, and
71
+ strict result normalization.
72
+ - Route every OpenCode run to an exact `provider/model` and validate model
73
+ variants, preserving the route the application requested and the route the
74
+ captured result reports.
75
+ - Prepare owner-private OpenCode config, data, cache, and state overlays for
76
+ each invocation, with scoped `read-only` and `workspace-write` policies,
77
+ deliberate credential forwarding, and idempotent cleanup.
78
+ - Check run/export capabilities, selected authentication source, cached model
79
+ inventory, and the exact requested route locally before an application
80
+ starts work.
81
+ - Correlate run and sanitized-export evidence into typed outcomes for
82
+ completion, authentication, configuration, CLI failure, malformed output,
83
+ cancellation, and timeout.
84
+ - Report input, output, cache-read, cache-write, reasoning, and cost evidence
85
+ while preserving the difference between unavailable data and numeric zero.
86
+ - Return prepared commands and normalized evidence through lifecycle-safe
87
+ values that fit an application's existing streaming, timeout, cancellation,
88
+ retry, and process-supervision stack.
89
+
3
90
  ## 0.1.1 - 2026-08-11
4
91
 
5
92
  - Expose each profile's immutable credential-environment key inventory so
data/README.md CHANGED
@@ -1,22 +1,43 @@
1
1
  # Agent CLI Runtime
2
2
 
3
- `agent-cli-runtime` is a small Ruby library for tools that integrate with
4
- locally installed headless agent CLIs. It ships immutable profiles for Claude
5
- Code, Codex CLI, Pi, and Grok CLI and compiles provider-neutral requests into
6
- argv/stdin. It also reports typed capability evidence, extracts usage from
7
- provider JSON events, and exposes an honest local diagnostic command.
3
+ Agent CLI Runtime gives Ruby applications one stable integration layer for
4
+ Claude Code, Codex CLI, Pi, Grok CLI, and OpenCode. It builds provider-specific
5
+ commands from a shared request model, checks local versions and named
6
+ capabilities, and normalizes usage and results afterward. Centralized profiles,
7
+ environment rules, and parsers make agent providers easier to add, switch, and
8
+ upgrade.
9
+
10
+ Version 0.2.0 adds first-class OpenCode support with exact `provider/model`
11
+ routing, isolated per-invocation configuration, scoped permission policies,
12
+ offline readiness checks, and typed outcome normalization. See the
13
+ [changelog](CHANGELOG.md) for the complete release notes.
14
+
15
+ ## Why use it?
16
+
17
+ Agent CLIs disagree on flags, prompt transport, configuration locations,
18
+ permission controls, usage events, and result formats. Agent CLI Runtime keeps
19
+ those differences in versioned profiles so application code can use one
20
+ request and result vocabulary.
21
+
22
+ - Add or switch agent CLIs without spreading provider conditionals throughout
23
+ the application.
24
+ - Validate installed versions, capabilities, and exact routes before starting
25
+ work.
26
+ - Preserve typed usage and outcome evidence across provider-specific output
27
+ formats.
28
+ - Add custom profiles through the same immutable compatibility contract.
8
29
 
9
30
  ## Install
10
31
 
11
32
  ```ruby
12
- gem "agent-cli-runtime", "~> 0.1.0"
33
+ gem "agent-cli-runtime", "~> 0.2.0"
13
34
  ```
14
35
 
15
36
  ```ruby
16
37
  require "agent_cli_runtime"
17
38
  ```
18
39
 
19
- Ruby 3.4 or newer is required. Version 0.1.x is tested on Linux and macOS.
40
+ Ruby 3.4 or newer is required. The 0.2.x line is tested on Linux and macOS.
20
41
 
21
42
  ## Compile an invocation
22
43
 
@@ -39,6 +60,11 @@ Compilation does not execute the returned command. Unsupported requested
39
60
  controls raise `AgentCliRuntime::UnsupportedCapability` with typed evidence
40
61
  instead of silently widening the request.
41
62
 
63
+ Prompt transport is profile-owned. `:stdin` writes the prompt to stdin and
64
+ adds the CLI's conventional `-` argv marker; `:piped_stdin` writes the prompt
65
+ to stdin without a marker for CLIs such as Pi that consume a non-TTY stream
66
+ directly. Built-in Pi therefore keeps arbitrarily large prompts out of argv.
67
+
42
68
  `permission_mode: nil` selects the profile's default non-interactive permission
43
69
  flags, which may include a provider's bypass flag. Pass `"read-only"` or
44
70
  `"workspace-write"` explicitly when the integration requires that constraint.
@@ -56,6 +82,16 @@ represent.
56
82
  - `extract_usage(profile, event)` normalizes provider usage when present and
57
83
  returns `nil` when usage is absent or malformed.
58
84
  - `observe(profile, result)` normalizes bounded, redacted result metadata.
85
+ - `prepare!(open_code_preparation)` returns a `PreparedInvocation` containing
86
+ an isolated OpenCode overlay, argv, and child environment.
87
+ - `OpenCode::Permissions.compile(...)` returns the same deny-first permission
88
+ document independently for callers that retain native OpenCode state.
89
+ - `parse_run(profile, stdout:)` parses a successful OpenCode JSONL capture
90
+ into the session and terminal-message identity required for inspection.
91
+ - `prepare_inspection(prepared, parsed_run)` returns the non-model sanitized
92
+ session-export invocation.
93
+ - `normalize(profile, captured, requested_route:)` returns one typed OpenCode
94
+ outcome from caller-captured run, termination, and inspection evidence.
59
95
 
60
96
  Provider arguments accept a built-in name or an `AgentCliRuntime::Profile`.
61
97
  Unknown built-in names raise `AgentCliRuntime::UnknownProvider`; they are not
@@ -108,6 +144,138 @@ where the CLI owns its subscription/session state. `configuration_directory`
108
144
  resolves that location from a caller-supplied home and environment without
109
145
  reading credentials or deciding authentication policy.
110
146
 
147
+ ## Prepare and normalize OpenCode
148
+
149
+ OpenCode `1.18.16+` requires an exact `provider/model` route and an explicit,
150
+ read-only configuration source. Configuration may define providers and an
151
+ exact default model, but it must not contain credential values. Name the
152
+ credential environment variables the caller is allowed to forward instead.
153
+
154
+ Callers that deliberately retain native OpenCode config and login can use
155
+ `OpenCode::Permissions.compile` directly and pass the JSON document through
156
+ OpenCode's per-process permission input. The prepared-overlay API below
157
+ remains the closed, isolated option.
158
+
159
+ ```ruby
160
+ require "agent_cli_runtime"
161
+ require "tmpdir"
162
+
163
+ route = "anthropic/claude-sonnet-4-5"
164
+ profile = AgentCliRuntime::Profiles.fetch(:opencode)
165
+ request = AgentCliRuntime::Request.new(
166
+ profile: profile,
167
+ prompt: "Make the requested atomic edit",
168
+ permission_mode: "workspace-write",
169
+ model: route,
170
+ effort: "high"
171
+ )
172
+ preparation = AgentCliRuntime::OpenCodePreparationRequest.new(
173
+ request: request,
174
+ working_directory: Dir.pwd,
175
+ invocation_root: File.join(Dir.tmpdir, "my-opencode-invocation"),
176
+ configuration: {
177
+ "model" => route,
178
+ "provider" => {
179
+ "anthropic" => { "npm" => "@ai-sdk/anthropic" }
180
+ }
181
+ },
182
+ credential_environment_keys: ["ANTHROPIC_API_KEY"],
183
+ additional_read_roots: [Dir.pwd],
184
+ additional_write_roots: [Dir.pwd],
185
+ bash_patterns: ["git*", "bundle*", "bin/*"]
186
+ )
187
+
188
+ prepared = AgentCliRuntime.prepare!(preparation)
189
+ begin
190
+ # The caller owns spawning, capture, timeout/cancellation, and process-tree
191
+ # cleanup. Forward only this selected environment to the child.
192
+ run_argv = prepared.invocation.argv
193
+ run_stdin = prepared.invocation.stdin_data
194
+ run_environment = prepared.environment_for(env: ENV)
195
+
196
+ # After a zero main-process exit, parse the captured JSONL and run the
197
+ # separately compiled, non-model sanitized export under the same overlay.
198
+ parsed = AgentCliRuntime.parse_run(:opencode, stdout: run_stdout)
199
+ inspection = AgentCliRuntime.prepare_inspection(prepared, parsed)
200
+ inspection_argv = inspection.argv
201
+ inspection_environment = inspection.environment_for(env: ENV)
202
+
203
+ captured = AgentCliRuntime::CapturedResult.new(
204
+ stdout: run_stdout,
205
+ stderr: run_stderr,
206
+ termination: AgentCliRuntime::TerminationEvidence.new(exit_code: 0),
207
+ inspection_output: sanitized_export_stdout
208
+ )
209
+ outcome = AgentCliRuntime.normalize(
210
+ :opencode, captured, requested_route: prepared.requested_route
211
+ )
212
+ ensure
213
+ prepared.cleanup! if prepared
214
+ end
215
+ ```
216
+
217
+ `run_stdout`, `run_stderr`, and `sanitized_export_stdout` above are captures
218
+ provided by the caller's process supervisor. Execute the inspection only after
219
+ a successful main run. For a timeout, cancellation, signal, or non-zero exit,
220
+ construct the matching `TerminationEvidence` and normalize without pretending
221
+ that incomplete output is a successful result.
222
+
223
+ Preparation creates owner-private config, data, cache, and state paths below
224
+ the fresh invocation root; redirects OpenCode into them; disables ambient
225
+ project/default discovery and remote model refresh; checks version, required
226
+ flags, selected auth, cached route, and requested variant locally; and returns
227
+ discrete argv/environment values. It never sends a prompt or model request.
228
+ `PreparedInvocation#cleanup!` removes only invocation-owned paths and is safe
229
+ to call twice. Call it from the process owner's `ensure` path after every
230
+ pre-spawn and post-spawn outcome.
231
+
232
+ `read-only` denies edits, shell, unsafe tools, and external writes.
233
+ `workspace-write` permits edits only under the declared write roots. Shell is
234
+ denied unless the caller supplies explicit OpenCode `bash_patterns`; those
235
+ patterns are application permissions, not an OS sandbox. A `nil` permission
236
+ mode is rejected unless the
237
+ consumer supplies an explicit typed `OpenCodePermissionPolicy`; the ordinary
238
+ preparation API never silently falls back to a bypass. Plugin sources are
239
+ explicit, and `--pure` remains enabled when no plugin was selected.
240
+
241
+ A completed outcome contains one bounded final assistant message, requested
242
+ and sanitized-export-observed routes, and nullable input/output/cache
243
+ read/cache write/reasoning/cost fields. Missing evidence stays `nil`; numeric
244
+ zero stays zero. Other outcome kinds are `authentication_failure`,
245
+ `configuration_failure`, `cli_failure`, `malformed_output`, `cancelled`, and
246
+ `timed_out`. Diagnostics and unknown-event summaries are bounded and redacted.
247
+
248
+ Maintainers can run the installed-CLI offline contract without a prompt or
249
+ model request:
250
+
251
+ ```sh
252
+ bundle exec ruby -Itest test/opencode_offline_smoke_test.rb
253
+ ```
254
+
255
+ Auto-discovery considers every matching executable in `PATH` and prefers an
256
+ already-installed native executable over an earlier package-manager launcher,
257
+ including script and multicall-binary shims, so the
258
+ smoke cannot trigger package-manager installation or refresh behavior. If only
259
+ a shim is discoverable, set `AGENT_CLI_RUNTIME_OPENCODE_OFFLINE_BIN` to the
260
+ native OpenCode executable explicitly.
261
+
262
+ The authenticated atomic-edit smoke is separately gated and refuses to run
263
+ without an explicit route, config path, credential variable name, opt-in, and
264
+ non-empty selected credential:
265
+
266
+ ```sh
267
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE=1 \
268
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE_ROUTE=anthropic/claude-sonnet-4-5 \
269
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE_CONFIG=/absolute/path/opencode.json \
270
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE_CREDENTIAL_ENV=ANTHROPIC_API_KEY \
271
+ ANTHROPIC_API_KEY=... \
272
+ bundle exec ruby -Itest test/opencode_live_test.rb
273
+ ```
274
+
275
+ The live test records only route, CLI version, outcome/usage availability, and
276
+ cleanup state. It does not print the credential or raw selected config. A
277
+ missing opt-in input is an explicit skip, not a deterministic-suite failure.
278
+
111
279
  ## Inspect local prerequisites
112
280
 
113
281
  ```ruby
@@ -126,7 +294,7 @@ agent-runtime probe --all --json
126
294
  ```
127
295
 
128
296
  The JSON contract is `{"schema_version":1,"probes":[...]}` and always orders
129
- all-provider output as `claude`, `codex`, `pi`, `grok`.
297
+ all-provider output as `claude`, `codex`, `pi`, `grok`, `opencode`.
130
298
 
131
299
  - Exit `0`: every requested local probe is ready.
132
300
  - Exit `1`: at least one requested local prerequisite is unavailable.
@@ -172,7 +340,7 @@ own provider-health policy.
172
340
  ## Compatibility
173
341
 
174
342
  Provider flags, event formats, and public value-object fields are
175
- SemVer-governed behavior. Additive fields are compatible within 0.1.x; removing
343
+ SemVer-governed behavior. Additive fields are compatible within 0.2.x; removing
176
344
  or changing an existing field or meaning requires a new minor version while the
177
345
  gem remains pre-1.0.
178
346
 
@@ -5,12 +5,14 @@ Gem::Specification.new do |spec|
5
5
  spec.version = AgentCliRuntime::VERSION
6
6
  spec.authors = [ "Ivan Kuznetsov" ]
7
7
  spec.email = [ "ivan@ikuznetsov.com" ]
8
- spec.summary = "Provider-neutral contracts for installed agent CLIs"
8
+ spec.summary =
9
+ "One Ruby API for Claude Code, Codex CLI, Pi, Grok CLI, and OpenCode"
9
10
  spec.description = <<~DESC
10
- Agent CLI Runtime provides immutable profiles, invocation compilation,
11
- local prerequisite probes, capability evidence, usage extraction, and
12
- result normalization for Claude Code, Codex CLI, Pi, and Grok CLI. It does
13
- not spawn agents or claim live provider health, quota, or credential validity.
11
+ Agent CLI Runtime gives Ruby applications one stable integration layer for
12
+ local coding-agent CLIs. It builds provider-specific commands from a shared
13
+ request model, checks local versions and named capabilities, and normalizes
14
+ usage and results afterward. Centralized profiles, environment rules, and
15
+ parsers make agent providers easier to add, switch, and upgrade.
14
16
  DESC
15
17
  spec.homepage = "https://github.com/ivankuznetsov/agent-cli-runtime"
16
18
  spec.license = "MIT"
@@ -0,0 +1,95 @@
1
+ module AgentCliRuntime
2
+ # Provider-side failures that a CLI reports on its event stream while still
3
+ # exiting zero. Each profile owns the shape its own CLI emits. Extractors
4
+ # return either the raw provider text, an ExtractedFailure, or nil when the
5
+ # event carries no failure. Runtime.extract_provider_error normalizes and
6
+ # redacts the result.
7
+ module ErrorExtractors
8
+ module_function
9
+
10
+ # CLIs that name the failure with a dedicated event type. This is the same
11
+ # set Hive scanned for inline before provider shape moved behind the
12
+ # profile, so wiring a profile to DEFAULT preserves its behaviour.
13
+ DEFAULT = lambda do |event|
14
+ next nil unless event.is_a?(Hash)
15
+
16
+ case event["type"]
17
+ when "error", "turn.failed", "rate_limit_event"
18
+ ErrorExtractors.message_from(event)
19
+ when "result"
20
+ if event["is_error"] == true || event["subtype"].to_s.start_with?("error")
21
+ ErrorExtractors.message_from(event)
22
+ end
23
+ end
24
+ end
25
+
26
+ # Claude's subscription wall is a dedicated structured event with no
27
+ # human-readable message. The generic extractor therefore returned nil,
28
+ # leaving callers to flatten a rejected five-hour/seven-day window into a
29
+ # generic exit-code failure. Accept only the exact rejected account-window
30
+ # shapes observed from Claude Code and synthesize bounded provider text for
31
+ # the normal typed limit path. Other Claude errors retain DEFAULT behavior.
32
+ CLAUDE_ACCOUNT_LIMIT_TYPES = %w[five_hour seven_day].freeze
33
+ CLAUDE = lambda do |event|
34
+ if event.is_a?(Hash) && event["type"] == "rate_limit_event"
35
+ info = event["rate_limit_info"]
36
+ if info.is_a?(Hash) && info["status"] == "rejected"
37
+ limit_type = info["rateLimitType"].to_s
38
+ if CLAUDE_ACCOUNT_LIMIT_TYPES.include?(limit_type)
39
+ next ExtractedFailure.new(
40
+ kind: :provider_limit,
41
+ message: "Claude account quota reached (#{limit_type})"
42
+ )
43
+ end
44
+ end
45
+ end
46
+
47
+ DEFAULT.call(event)
48
+ end
49
+
50
+ # pi keeps the envelope type ("message_start"/"message_end") and moves the
51
+ # terminal state into stopReason. Provider refusals use
52
+ # stopReason=error/errorMessage. A model that consumes its entire output
53
+ # allowance uses stopReason=length and can exit zero without executing the
54
+ # final write tool. Matching on type alone sees neither failure.
55
+ PI = lambda do |event|
56
+ next nil unless event.is_a?(Hash)
57
+
58
+ message = event["message"].is_a?(Hash) ? event["message"] : event
59
+ case message["stopReason"].to_s
60
+ when "error"
61
+ ErrorExtractors.message_from(message) || ErrorExtractors.message_from(event)
62
+ when "length"
63
+ ExtractedFailure.new(
64
+ kind: :model_output_limit,
65
+ message: "model response reached its maximum output tokens"
66
+ )
67
+ end
68
+ end
69
+
70
+ # OpenCode keeps provider failures under error.data.message rather than
71
+ # the top-level/message and error.message shapes used by the default
72
+ # extractor. Read only the dedicated error event so ordinary model text
73
+ # cannot forge a provider-limit signal.
74
+ OPENCODE = lambda do |event|
75
+ next nil unless event.is_a?(Hash) && event["type"] == "error"
76
+
77
+ data = event.dig("error", "data")
78
+ message = data["message"] if data.is_a?(Hash)
79
+ message if message.is_a?(String) && !message.strip.empty?
80
+ end
81
+
82
+ def message_from(event)
83
+ return nil unless event.is_a?(Hash)
84
+
85
+ candidates = [
86
+ event["errorMessage"],
87
+ event["message"],
88
+ event.dig("error", "message"),
89
+ event["error"]
90
+ ]
91
+ text = candidates.find { |value| value.is_a?(String) && !value.strip.empty? }
92
+ text&.strip
93
+ end
94
+ end
95
+ end
@@ -14,4 +14,11 @@ module AgentCliRuntime
14
14
  class ProbeError < Error; end
15
15
  class CompilationError < Error; end
16
16
  class UnknownProvider < Error; end
17
+ class PreparationError < Error; end
18
+ class ConfigurationError < PreparationError; end
19
+ class AuthenticationError < ProbeError; end
20
+ class RouteUnavailable < ProbeError; end
21
+ class UnsafePathError < PreparationError; end
22
+ class ResultError < Error; end
23
+ class MalformedOutput < ResultError; end
17
24
  end
@@ -0,0 +1,32 @@
1
+ module AgentCliRuntime
2
+ module OpenCode
3
+ module Inspection
4
+ module_function
5
+
6
+ def compile(prepared, parsed_run)
7
+ unless prepared.is_a?(PreparedInvocation)
8
+ raise ArgumentError,
9
+ "prepared must be an AgentCliRuntime::PreparedInvocation"
10
+ end
11
+ unless parsed_run.is_a?(ParsedRun)
12
+ raise ArgumentError, "parsed_run must be an AgentCliRuntime::ParsedRun"
13
+ end
14
+ unless prepared.invocation.provider == :opencode
15
+ raise ConfigurationError,
16
+ "sanitized export inspection requires an OpenCode invocation"
17
+ end
18
+
19
+ InspectionCommand.new(
20
+ argv: [
21
+ prepared.executable, "export", parsed_run.session_id, "--sanitize"
22
+ ],
23
+ stdin_data: nil,
24
+ environment: prepared.environment,
25
+ credential_environment_keys: prepared.credential_environment_keys,
26
+ session_id: parsed_run.session_id,
27
+ message_id: parsed_run.terminal_message_id
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end