agent-cli-runtime 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59315cc0411f928db6beb1ecea2a7f96986c26e7436ea60d14d105c7b0252c71
4
- data.tar.gz: 073dfc6f68dabb86a4307a6dbf0a61a3238f6dea32256ac04432d174ea8acf6f
3
+ metadata.gz: 38323f74e355a6a9da1dfb45ccee48c253ffbd0c9693a16cd62910ced381c7cb
4
+ data.tar.gz: 2e38766e9b737df1e5360ac8013849cf2108dab3f8b937f24ec393c001aba669
5
5
  SHA512:
6
- metadata.gz: abca7ef13599eae3e161b085ee97e334a3a665fd9a8de01ae0bbb3719cb9034edda857e6ad12c188936cd3b2e696811488b28505c6aa8edf6cff25aabda02959
7
- data.tar.gz: e8d831b5884967165f4422472b6bce5510f76fd581173955536eadd871e851358f3017a851865cc64530b0ffa42b47c8bfe5549d709978e8ef6a54b270a2f30b
6
+ metadata.gz: 95e1213e6586ea162e2a12294b350d4ea5190c8f09239e276fe4de17ceb1a7787c0c4979e3e71c6acb0f8e318facea099820a221130b123549d8a123c13e62b7
7
+ data.tar.gz: 1cca33e3af240a7abf5b8627d69256ee43647557fc2aeeab4f74692c7ba08ff74a0b0a5cdef202796f4559cc04d0498138762003fd14845c61029bd0b7603231
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.2.0 - 2026-08-15
6
+
7
+ - Add OpenCode `1.18.16+` as a fifth immutable built-in profile with exact
8
+ `provider/model` routing and faithful model-variant validation.
9
+ - Add route-aware offline probing for the required run/export flags, selected
10
+ authentication source, cached model inventory, and exact requested route.
11
+ - Add invocation-owned OpenCode config/data/cache/state overlays with
12
+ deny-first `read-only` and `workspace-write` policies, explicit credential
13
+ forwarding, owner-private resources, and idempotent cleanup.
14
+ - Add strict run/export correlation and typed outcomes for completion,
15
+ authentication/configuration/CLI failure, malformed output, cancellation,
16
+ and timeout while preserving requested versus actual route identity.
17
+ - Preserve unavailable separately from numeric zero for input, output,
18
+ cache-read, cache-write, reasoning, and cost evidence.
19
+ - Keep process spawning, streaming, timeout/cancellation supervision,
20
+ process-tree cleanup, retries, and post-run inspection execution with the
21
+ caller; the component returns commands and normalizes captured evidence.
22
+
3
23
  ## 0.1.1 - 2026-08-11
4
24
 
5
25
  - Expose each profile's immutable credential-environment key inventory so
data/README.md CHANGED
@@ -2,21 +2,23 @@
2
2
 
3
3
  `agent-cli-runtime` is a small Ruby library for tools that integrate with
4
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.
5
+ Code, Codex CLI, Pi, Grok CLI, and OpenCode and compiles provider-neutral
6
+ requests into argv/stdin. It also reports typed capability evidence, extracts
7
+ usage from provider JSON events, and exposes an honest local diagnostic
8
+ command. OpenCode adds an invocation-owned overlay and strict captured-result
9
+ normalizer while leaving process supervision with the caller.
8
10
 
9
11
  ## Install
10
12
 
11
13
  ```ruby
12
- gem "agent-cli-runtime", "~> 0.1.0"
14
+ gem "agent-cli-runtime", "~> 0.2.0"
13
15
  ```
14
16
 
15
17
  ```ruby
16
18
  require "agent_cli_runtime"
17
19
  ```
18
20
 
19
- Ruby 3.4 or newer is required. Version 0.1.x is tested on Linux and macOS.
21
+ Ruby 3.4 or newer is required. The 0.2.x line is tested on Linux and macOS.
20
22
 
21
23
  ## Compile an invocation
22
24
 
@@ -56,6 +58,14 @@ represent.
56
58
  - `extract_usage(profile, event)` normalizes provider usage when present and
57
59
  returns `nil` when usage is absent or malformed.
58
60
  - `observe(profile, result)` normalizes bounded, redacted result metadata.
61
+ - `prepare!(open_code_preparation)` creates and probes an isolated OpenCode
62
+ overlay and returns a `PreparedInvocation`; it does not spawn OpenCode.
63
+ - `parse_run(profile, stdout:)` parses a successful OpenCode JSONL capture
64
+ into the session and terminal-message identity required for inspection.
65
+ - `prepare_inspection(prepared, parsed_run)` compiles the non-model sanitized
66
+ session-export command without executing it.
67
+ - `normalize(profile, captured, requested_route:)` returns one typed OpenCode
68
+ outcome from caller-captured run, termination, and inspection evidence.
59
69
 
60
70
  Provider arguments accept a built-in name or an `AgentCliRuntime::Profile`.
61
71
  Unknown built-in names raise `AgentCliRuntime::UnknownProvider`; they are not
@@ -108,6 +118,128 @@ where the CLI owns its subscription/session state. `configuration_directory`
108
118
  resolves that location from a caller-supplied home and environment without
109
119
  reading credentials or deciding authentication policy.
110
120
 
121
+ ## Prepare and normalize OpenCode
122
+
123
+ OpenCode `1.18.16+` requires an exact `provider/model` route and an explicit,
124
+ read-only configuration source. Configuration may define providers and an
125
+ exact default model, but it must not contain credential values. Name the
126
+ credential environment variables the caller is allowed to forward instead.
127
+
128
+ ```ruby
129
+ require "agent_cli_runtime"
130
+ require "tmpdir"
131
+
132
+ route = "anthropic/claude-sonnet-4-5"
133
+ profile = AgentCliRuntime::Profiles.fetch(:opencode)
134
+ request = AgentCliRuntime::Request.new(
135
+ profile: profile,
136
+ prompt: "Make the requested atomic edit",
137
+ permission_mode: "workspace-write",
138
+ model: route,
139
+ effort: "high"
140
+ )
141
+ preparation = AgentCliRuntime::OpenCodePreparationRequest.new(
142
+ request: request,
143
+ working_directory: Dir.pwd,
144
+ invocation_root: File.join(Dir.tmpdir, "my-opencode-invocation"),
145
+ configuration: {
146
+ "model" => route,
147
+ "provider" => {
148
+ "anthropic" => { "npm" => "@ai-sdk/anthropic" }
149
+ }
150
+ },
151
+ credential_environment_keys: ["ANTHROPIC_API_KEY"],
152
+ additional_read_roots: [Dir.pwd],
153
+ additional_write_roots: [Dir.pwd]
154
+ )
155
+
156
+ prepared = AgentCliRuntime.prepare!(preparation)
157
+ begin
158
+ # The caller owns spawning, capture, timeout/cancellation, and process-tree
159
+ # cleanup. Forward only this selected environment to the child.
160
+ run_argv = prepared.invocation.argv
161
+ run_stdin = prepared.invocation.stdin_data
162
+ run_environment = prepared.environment_for(env: ENV)
163
+
164
+ # After a zero main-process exit, parse the captured JSONL and run the
165
+ # separately compiled, non-model sanitized export under the same overlay.
166
+ parsed = AgentCliRuntime.parse_run(:opencode, stdout: run_stdout)
167
+ inspection = AgentCliRuntime.prepare_inspection(prepared, parsed)
168
+ inspection_argv = inspection.argv
169
+ inspection_environment = inspection.environment_for(env: ENV)
170
+
171
+ captured = AgentCliRuntime::CapturedResult.new(
172
+ stdout: run_stdout,
173
+ stderr: run_stderr,
174
+ termination: AgentCliRuntime::TerminationEvidence.new(exit_code: 0),
175
+ inspection_output: sanitized_export_stdout
176
+ )
177
+ outcome = AgentCliRuntime.normalize(
178
+ :opencode, captured, requested_route: prepared.requested_route
179
+ )
180
+ ensure
181
+ prepared.cleanup! if prepared
182
+ end
183
+ ```
184
+
185
+ `run_stdout`, `run_stderr`, and `sanitized_export_stdout` above are captures
186
+ provided by the caller's process supervisor. Execute the inspection only after
187
+ a successful main run. For a timeout, cancellation, signal, or non-zero exit,
188
+ construct the matching `TerminationEvidence` and normalize without pretending
189
+ that incomplete output is a successful result.
190
+
191
+ Preparation creates owner-private config, data, cache, and state paths below
192
+ the fresh invocation root; redirects OpenCode into them; disables ambient
193
+ project/default discovery and remote model refresh; checks version, required
194
+ flags, selected auth, cached route, and requested variant locally; and returns
195
+ discrete argv/environment values. It never sends a prompt or model request.
196
+ `PreparedInvocation#cleanup!` removes only invocation-owned paths and is safe
197
+ to call twice. Call it from the process owner's `ensure` path after every
198
+ pre-spawn and post-spawn outcome.
199
+
200
+ `read-only` denies edits, shell, unsafe tools, and external writes.
201
+ `workspace-write` permits edits only under the declared write roots and still
202
+ denies unrestricted shell. A `nil` permission mode is rejected unless the
203
+ consumer supplies an explicit typed `OpenCodePermissionPolicy`; the ordinary
204
+ preparation API never silently falls back to a bypass. Plugin sources are
205
+ explicit, and `--pure` remains enabled when no plugin was selected.
206
+
207
+ A completed outcome contains one bounded final assistant message, requested
208
+ and sanitized-export-observed routes, and nullable input/output/cache
209
+ read/cache write/reasoning/cost fields. Missing evidence stays `nil`; numeric
210
+ zero stays zero. Other outcome kinds are `authentication_failure`,
211
+ `configuration_failure`, `cli_failure`, `malformed_output`, `cancelled`, and
212
+ `timed_out`. Diagnostics and unknown-event summaries are bounded and redacted.
213
+
214
+ Maintainers can run the installed-CLI offline contract without a prompt or
215
+ model request:
216
+
217
+ ```sh
218
+ bundle exec ruby -Itest test/opencode_offline_smoke_test.rb
219
+ ```
220
+
221
+ If an installation command is itself a package-manager shim, set
222
+ `AGENT_CLI_RUNTIME_OPENCODE_OFFLINE_BIN` to the already-installed native
223
+ OpenCode executable so the smoke cannot trigger shim installation or refresh
224
+ behavior.
225
+
226
+ The authenticated atomic-edit smoke is separately gated and refuses to run
227
+ without an explicit route, config path, credential variable name, opt-in, and
228
+ non-empty selected credential:
229
+
230
+ ```sh
231
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE=1 \
232
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE_ROUTE=anthropic/claude-sonnet-4-5 \
233
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE_CONFIG=/absolute/path/opencode.json \
234
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE_CREDENTIAL_ENV=ANTHROPIC_API_KEY \
235
+ ANTHROPIC_API_KEY=... \
236
+ bundle exec ruby -Itest test/opencode_live_test.rb
237
+ ```
238
+
239
+ The live test records only route, CLI version, outcome/usage availability, and
240
+ cleanup state. It does not print the credential or raw selected config. A
241
+ missing opt-in input is an explicit skip, not a deterministic-suite failure.
242
+
111
243
  ## Inspect local prerequisites
112
244
 
113
245
  ```ruby
@@ -126,7 +258,7 @@ agent-runtime probe --all --json
126
258
  ```
127
259
 
128
260
  The JSON contract is `{"schema_version":1,"probes":[...]}` and always orders
129
- all-provider output as `claude`, `codex`, `pi`, `grok`.
261
+ all-provider output as `claude`, `codex`, `pi`, `grok`, `opencode`.
130
262
 
131
263
  - Exit `0`: every requested local probe is ready.
132
264
  - Exit `1`: at least one requested local prerequisite is unavailable.
@@ -172,7 +304,7 @@ own provider-health policy.
172
304
  ## Compatibility
173
305
 
174
306
  Provider flags, event formats, and public value-object fields are
175
- SemVer-governed behavior. Additive fields are compatible within 0.1.x; removing
307
+ SemVer-governed behavior. Additive fields are compatible within 0.2.x; removing
176
308
  or changing an existing field or meaning requires a new minor version while the
177
309
  gem remains pre-1.0.
178
310
 
@@ -9,8 +9,9 @@ Gem::Specification.new do |spec|
9
9
  spec.description = <<~DESC
10
10
  Agent CLI Runtime provides immutable profiles, invocation compilation,
11
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.
12
+ result normalization for Claude Code, Codex CLI, Pi, Grok CLI, and
13
+ OpenCode. It does not spawn agents or claim live provider health, quota,
14
+ or credential validity.
14
15
  DESC
15
16
  spec.homepage = "https://github.com/ivankuznetsov/agent-cli-runtime"
16
17
  spec.license = "MIT"
@@ -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