coding-cli-runtime 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

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.
@@ -2,8 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.3.0"
6
-
7
5
  from .auth import AuthResolution, resolve_auth
8
6
  from .codex_cli import CodexExecSpec, build_codex_exec_spec
9
7
  from .contracts import (
@@ -24,16 +22,25 @@ from .headless import (
24
22
  from .provider_contracts import (
25
23
  ApprovalContract,
26
24
  AuthContract,
25
+ DiagnosticsContract,
27
26
  HeadlessContract,
27
+ IoContract,
28
+ OutputContract,
28
29
  PathContract,
29
30
  PromptPayload,
30
31
  PromptTransport,
31
32
  ProviderContract,
32
33
  SandboxContract,
34
+ SessionDiscoveryContract,
35
+ WorkspaceEnvValueSource,
36
+ WorkspaceEnvVar,
33
37
  build_env_overlay,
34
38
  get_provider_contract,
39
+ is_provider_installed,
35
40
  render_prompt,
36
41
  resolve_config_paths,
42
+ resolve_session_search_paths,
43
+ resolve_workspace_env,
37
44
  )
38
45
  from .provider_controls import build_model_id, resolve_provider_model_controls
39
46
  from .provider_specs import (
@@ -75,6 +82,8 @@ from .session_logs import (
75
82
  )
76
83
  from .subprocess_runner import run_cli_command, run_cli_command_sync
77
84
 
85
+ __version__ = "0.4.0"
86
+
78
87
  __all__ = [
79
88
  "ApprovalContract",
80
89
  "AuthContract",
@@ -87,10 +96,13 @@ __all__ = [
87
96
  "ClaudeReasoningPolicy",
88
97
  "CliLaunchSpec",
89
98
  "ControlSpec",
99
+ "DiagnosticsContract",
90
100
  "ErrorCode",
91
101
  "FailureClassification",
92
102
  "HeadlessContract",
103
+ "IoContract",
93
104
  "ModelSpec",
105
+ "OutputContract",
94
106
  "PathContract",
95
107
  "PromptPayload",
96
108
  "PromptTransport",
@@ -98,6 +110,9 @@ __all__ = [
98
110
  "ProviderSpec",
99
111
  "SandboxContract",
100
112
  "SchemaValidationError",
113
+ "SessionDiscoveryContract",
114
+ "WorkspaceEnvVar",
115
+ "WorkspaceEnvValueSource",
101
116
  "InteractiveCliRunResult",
102
117
  "SessionProgressEvent",
103
118
  "SessionRetryDecision",
@@ -121,6 +136,7 @@ __all__ = [
121
136
  "get_gemini_model_options",
122
137
  "get_provider_contract",
123
138
  "get_provider_spec",
139
+ "is_provider_installed",
124
140
  "list_provider_specs",
125
141
  "build_model_id",
126
142
  "classify_provider_failure",
@@ -130,6 +146,8 @@ __all__ = [
130
146
  "resolve_claude_reasoning_policy",
131
147
  "resolve_config_paths",
132
148
  "resolve_provider_model_controls",
149
+ "resolve_session_search_paths",
150
+ "resolve_workspace_env",
133
151
  "redact_text",
134
152
  "claude_project_key",
135
153
  "find_claude_session",
@@ -5,7 +5,8 @@ config paths, and headless launch conventions. It exposes frozen dataclasses
5
5
  that consumers can read selectively — no obligation to use the full structure.
6
6
 
7
7
  Public stable API:
8
- get_provider_contract, build_env_overlay, resolve_config_paths, render_prompt
8
+ get_provider_contract, build_env_overlay, resolve_config_paths, render_prompt,
9
+ resolve_workspace_env, resolve_session_search_paths, is_provider_installed
9
10
 
10
11
  Internal (not exported from __init__):
11
12
  _build_non_interactive_run
@@ -13,8 +14,10 @@ Internal (not exported from __init__):
13
14
 
14
15
  from __future__ import annotations
15
16
 
17
+ import shutil
16
18
  from dataclasses import dataclass
17
19
  from pathlib import Path
20
+ from typing import Literal, TypeAlias
18
21
 
19
22
  from .contracts import AuthMode
20
23
 
@@ -105,6 +108,51 @@ class HeadlessContract:
105
108
  default_stream_mode: str | None
106
109
 
107
110
 
111
+ @dataclass(frozen=True)
112
+ class OutputContract:
113
+ """How the CLI delivers structured output."""
114
+
115
+ format_flag: str | None
116
+ supported_formats: tuple[str, ...]
117
+ default_format: str | None
118
+ output_path_flag: str | None
119
+ schema_path_flag: str | None
120
+
121
+
122
+ WorkspaceEnvValueSource: TypeAlias = Literal["execution_dir", "workspace_root"]
123
+
124
+
125
+ @dataclass(frozen=True)
126
+ class WorkspaceEnvVar:
127
+ """An environment variable expected by the provider CLI."""
128
+
129
+ name: str
130
+ value_source: WorkspaceEnvValueSource
131
+
132
+
133
+ @dataclass(frozen=True)
134
+ class IoContract:
135
+ """Provider-specific I/O conventions beyond prompt transport."""
136
+
137
+ file_reference_prefix: str | None
138
+ workspace_env_vars: tuple[WorkspaceEnvVar, ...]
139
+
140
+
141
+ @dataclass(frozen=True)
142
+ class SessionDiscoveryContract:
143
+ """Where session logs live and how to find them."""
144
+
145
+ session_roots: tuple[str, ...]
146
+ session_glob: str
147
+
148
+
149
+ @dataclass(frozen=True)
150
+ class DiagnosticsContract:
151
+ """Where provider diagnostic logs live."""
152
+
153
+ log_glob: str
154
+
155
+
108
156
  @dataclass(frozen=True)
109
157
  class ProviderContract:
110
158
  """Structured metadata about a provider CLI.
@@ -118,6 +166,10 @@ class ProviderContract:
118
166
  auth: AuthContract
119
167
  paths: PathContract
120
168
  headless: HeadlessContract
169
+ output: OutputContract
170
+ io: IoContract
171
+ session_discovery: SessionDiscoveryContract | None
172
+ diagnostics: DiagnosticsContract | None
121
173
  notes: tuple[str, ...]
122
174
 
123
175
 
@@ -178,6 +230,22 @@ _CLAUDE_CONTRACT = ProviderContract(
178
230
  stream_modes=None,
179
231
  default_stream_mode=None,
180
232
  ),
233
+ output=OutputContract(
234
+ format_flag="--output-format",
235
+ supported_formats=("text", "json", "stream-json"),
236
+ default_format="text",
237
+ output_path_flag=None,
238
+ schema_path_flag=None,
239
+ ),
240
+ io=IoContract(
241
+ file_reference_prefix=None,
242
+ workspace_env_vars=(),
243
+ ),
244
+ session_discovery=SessionDiscoveryContract(
245
+ session_roots=("projects",),
246
+ session_glob="*/conversation.jsonl",
247
+ ),
248
+ diagnostics=None,
181
249
  notes=(),
182
250
  )
183
251
 
@@ -217,6 +285,22 @@ _CODEX_CONTRACT = ProviderContract(
217
285
  stream_modes=None,
218
286
  default_stream_mode=None,
219
287
  ),
288
+ output=OutputContract(
289
+ format_flag=None,
290
+ supported_formats=("json",),
291
+ default_format="json",
292
+ output_path_flag="-o",
293
+ schema_path_flag="--output-schema",
294
+ ),
295
+ io=IoContract(
296
+ file_reference_prefix=None,
297
+ workspace_env_vars=(),
298
+ ),
299
+ session_discovery=SessionDiscoveryContract(
300
+ session_roots=("sessions", "archived_sessions"),
301
+ session_glob="*.jsonl",
302
+ ),
303
+ diagnostics=None,
220
304
  notes=(
221
305
  "codex exec defaults to a read-only sandbox in non-interactive mode; "
222
306
  "use --sandbox danger-full-access for write access.",
@@ -258,9 +342,32 @@ _GEMINI_CONTRACT = ProviderContract(
258
342
  stream_modes=None,
259
343
  default_stream_mode=None,
260
344
  ),
345
+ output=OutputContract(
346
+ format_flag=None,
347
+ supported_formats=(),
348
+ default_format=None,
349
+ output_path_flag=None,
350
+ schema_path_flag=None,
351
+ ),
352
+ io=IoContract(
353
+ file_reference_prefix="@",
354
+ workspace_env_vars=(
355
+ WorkspaceEnvVar(
356
+ name="GEMINI_CLI_IDE_WORKSPACE_PATH",
357
+ value_source="execution_dir",
358
+ ),
359
+ ),
360
+ ),
361
+ session_discovery=SessionDiscoveryContract(
362
+ session_roots=("tmp",),
363
+ session_glob="*/chats/session-*.json",
364
+ ),
365
+ diagnostics=None,
261
366
  notes=(
262
367
  'Gemini requires --prompt "" to activate headless mode; '
263
368
  "the real prompt is delivered on stdin.",
369
+ "Gemini output format is prompt-directed, not CLI-flag-driven.",
370
+ "File references in prompts use @filename syntax.",
264
371
  ),
265
372
  )
266
373
 
@@ -295,6 +402,24 @@ _COPILOT_CONTRACT = ProviderContract(
295
402
  stream_modes=("on", "off"),
296
403
  default_stream_mode="on",
297
404
  ),
405
+ output=OutputContract(
406
+ format_flag=None,
407
+ supported_formats=("markdown",),
408
+ default_format="markdown",
409
+ output_path_flag="--share",
410
+ schema_path_flag=None,
411
+ ),
412
+ io=IoContract(
413
+ file_reference_prefix=None,
414
+ workspace_env_vars=(),
415
+ ),
416
+ session_discovery=SessionDiscoveryContract(
417
+ session_roots=("session-state",),
418
+ session_glob="*/events.jsonl",
419
+ ),
420
+ diagnostics=DiagnosticsContract(
421
+ log_glob="logs/process-*.log",
422
+ ),
298
423
  notes=(
299
424
  "Copilot default auth is CLI login (api_key_env_var is None). "
300
425
  "BYOK is available via COPILOT_PROVIDER_API_KEY.",
@@ -350,6 +475,33 @@ def build_env_overlay(
350
475
  return overlay
351
476
 
352
477
 
478
+ def resolve_workspace_env(
479
+ contract: ProviderContract,
480
+ execution_dir: str | Path,
481
+ *,
482
+ workspace_root: str | Path | None = None,
483
+ ) -> dict[str, str]:
484
+ """Resolve provider workspace env vars from contract metadata."""
485
+ resolved: dict[str, str] = {}
486
+ execution_dir_str = str(Path(execution_dir).expanduser())
487
+ workspace_root_str = None
488
+ if workspace_root is not None:
489
+ workspace_root_str = str(Path(workspace_root).expanduser())
490
+
491
+ for item in contract.io.workspace_env_vars:
492
+ if item.value_source == "execution_dir":
493
+ resolved[item.name] = execution_dir_str
494
+ continue
495
+ if item.value_source == "workspace_root":
496
+ if workspace_root_str is None:
497
+ raise ValueError(f"{item.name} requires workspace_root, but none was provided")
498
+ resolved[item.name] = workspace_root_str
499
+ continue
500
+ raise ValueError(f"Unknown workspace env value source: {item.value_source!r}")
501
+
502
+ return resolved
503
+
504
+
353
505
  def resolve_config_paths(
354
506
  contract: ProviderContract,
355
507
  *,
@@ -366,6 +518,23 @@ def resolve_config_paths(
366
518
  return host, host
367
519
 
368
520
 
521
+ def resolve_session_search_paths(
522
+ contract: ProviderContract,
523
+ *,
524
+ config_dir: str | Path | None = None,
525
+ ) -> tuple[Path, ...]:
526
+ """Expand contract session roots into concrete host paths."""
527
+ discovery = contract.session_discovery
528
+ if discovery is None:
529
+ return ()
530
+ base_dir = (
531
+ Path(config_dir).expanduser()
532
+ if config_dir is not None
533
+ else Path(contract.paths.config_dir).expanduser()
534
+ )
535
+ return tuple(base_dir / root for root in discovery.session_roots)
536
+
537
+
369
538
  def render_prompt(
370
539
  transport: PromptTransport,
371
540
  prompt: str,
@@ -387,6 +556,12 @@ def render_prompt(
387
556
  raise ValueError(f"Unknown prompt delivery mode: {transport.delivery!r}")
388
557
 
389
558
 
559
+ def is_provider_installed(provider_id: str) -> bool:
560
+ """Return whether the provider CLI binary is available on PATH."""
561
+ contract = get_provider_contract(provider_id)
562
+ return shutil.which(contract.binary) is not None
563
+
564
+
390
565
  # ---------------------------------------------------------------------------
391
566
  # Private builder (internal convenience, not public API)
392
567
  # ---------------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coding-cli-runtime
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Reusable CLI runtime primitives for provider-backed automation workflows
5
5
  Author-email: LLM Eval maintainers <llm-eval-maintainers@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -152,6 +152,44 @@ else:
152
152
  Works for all four providers. Recognizes auth failures, rate limits,
153
153
  network transients, and other provider-specific error patterns.
154
154
 
155
+ ### Common integration tasks
156
+
157
+ #### Check whether a provider CLI is installed
158
+
159
+ ```python
160
+ from coding_cli_runtime import is_provider_installed
161
+
162
+ if not is_provider_installed("claude"):
163
+ raise RuntimeError("Claude Code is not available on PATH")
164
+ ```
165
+
166
+ This is intentionally minimal: it checks whether the provider binary exists on
167
+ PATH. Deeper CLI drift validation belongs in maintainer tooling, not the
168
+ runtime API.
169
+
170
+ #### Resolve workspace env vars and session search paths
171
+
172
+ ```python
173
+ from coding_cli_runtime import (
174
+ get_provider_contract,
175
+ resolve_session_search_paths,
176
+ resolve_workspace_env,
177
+ )
178
+
179
+ gemini = get_provider_contract("gemini")
180
+
181
+ # Derive provider-specific workspace env vars from contract metadata
182
+ env = resolve_workspace_env(gemini, "/tmp/run-dir")
183
+ # {"GEMINI_CLI_IDE_WORKSPACE_PATH": "/tmp/run-dir"}
184
+
185
+ # Expand concrete host paths for session log searches
186
+ paths = resolve_session_search_paths(gemini)
187
+ # (Path.home() / ".gemini" / "tmp",)
188
+ ```
189
+
190
+ Use these helpers when you want the contract facts turned into concrete
191
+ filesystem/env values without rebuilding the same glue logic in each consumer.
192
+
155
193
  ### Look up provider contract metadata
156
194
 
157
195
  ```python
@@ -179,11 +217,43 @@ payload = render_prompt(contract.headless.prompt, "Fix the bug")
179
217
  ```
180
218
 
181
219
  `ProviderContract` is structured as nested sub-contracts
182
- (`AuthContract`, `PathContract`, `HeadlessContract`) so consumers
220
+ (`AuthContract`, `PathContract`, `HeadlessContract`, `OutputContract`,
221
+ `IoContract`, `SessionDiscoveryContract`, `DiagnosticsContract`) so consumers
183
222
  can drill into whichever aspect they need. This is reference metadata,
184
- not a command-construction control plane — consumers keep their own
223
+ not a command-construction control plane — callers keep their own
185
224
  command assembly and adopt contract fields selectively.
186
225
 
226
+ ### Query provider I/O conventions
227
+
228
+ ```python
229
+ from coding_cli_runtime import get_provider_contract
230
+
231
+ gemini = get_provider_contract("gemini")
232
+
233
+ # Workspace env vars with value semantics
234
+ for wev in gemini.io.workspace_env_vars:
235
+ print(f"{wev.name} = {wev.value_source}")
236
+ # GEMINI_CLI_IDE_WORKSPACE_PATH = execution_dir
237
+
238
+ # Session discovery (where session logs live)
239
+ sd = gemini.session_discovery
240
+ print(sd.session_roots) # ("tmp",)
241
+ print(sd.session_glob) # "*/chats/session-*.json"
242
+
243
+ # Output format support
244
+ codex = get_provider_contract("codex")
245
+ print(codex.output.output_path_flag) # "-o"
246
+ print(codex.output.schema_path_flag) # "--output-schema"
247
+
248
+ # Diagnostics (Copilot only)
249
+ copilot = get_provider_contract("copilot")
250
+ if copilot.diagnostics:
251
+ print(copilot.diagnostics.log_glob) # "logs/process-*.log"
252
+ ```
253
+
254
+ `WorkspaceEnvVar.value_source` uses a closed vocabulary:
255
+ `"execution_dir"` or `"workspace_root"`.
256
+
187
257
  ### Build headless launch commands
188
258
 
189
259
  ```python
@@ -224,7 +294,8 @@ files matching the working directory and time window.
224
294
  | `CliRunResult` | Result: returncode, stdout/stderr, duration, error code |
225
295
  | `ErrorCode` | `none` · `spawn_failed` · `timed_out` · `non_zero_exit` |
226
296
  | `ProviderSpec` | Provider catalog entry with models, controls, defaults |
227
- | `ProviderContract` | Structured provider CLI metadata (auth, paths, headless launch) |
297
+ | `ProviderContract` | Structured provider CLI metadata (auth, paths, headless, I/O, sessions) |
298
+ | `WorkspaceEnvVar` | Env var with value-source semantics (`execution_dir`, `workspace_root`) |
228
299
  | `FailureClassification` | Classified error with retryable flag and category |
229
300
 
230
301
  ### Run long-lived CLI sessions
@@ -249,7 +320,7 @@ result = await run_interactive_session(
249
320
  ```
250
321
 
251
322
  Only `cmd_parts`, `cwd`, `stdin_text`, and `logger` are required.
252
- Observability labels (`job_name`, `phase_tag`) default to sensible values.
323
+ Other parameters have sensible defaults.
253
324
 
254
325
  ## API summary
255
326
 
@@ -260,10 +331,11 @@ Key function groups:
260
331
  |-------|-----------|
261
332
  | Execution | `run_cli_command`, `run_cli_command_sync`, `run_interactive_session` |
262
333
  | Provider metadata | `get_provider_contract`, `get_provider_spec`, `list_provider_specs` |
263
- | Contract helpers | `build_env_overlay`, `resolve_config_paths`, `render_prompt`, `resolve_auth` |
334
+ | Contract helpers | `build_env_overlay`, `resolve_config_paths`, `render_prompt`, `resolve_auth`, `resolve_workspace_env`, `resolve_session_search_paths` |
264
335
  | Headless launch | `build_claude_headless_core`, `build_codex_headless_core`, `build_copilot_headless_core`, `build_gemini_headless_core` |
265
336
  | Codex batch | `build_codex_exec_spec` |
266
337
  | Failure handling | `classify_provider_failure` |
338
+ | Installation check | `is_provider_installed` |
267
339
  | Session logs | `find_codex_session`, `find_claude_session` |
268
340
  | Schema | `load_schema`, `validate_payload` |
269
341
  | Utilities | `redact_text`, `build_model_id`, `normalize_path_str` |
@@ -1,4 +1,4 @@
1
- coding_cli_runtime/__init__.py,sha256=S_dSga-xYos95nGC4yq8gQ2SvAUrpERnAFyJ-_SZDkY,3845
1
+ coding_cli_runtime/__init__.py,sha256=06_cwfOA9AMqD6oj5H7_uGj1405leIL6eYc_vCFOREg,4321
2
2
  coding_cli_runtime/auth.py,sha256=XP3TZINazvzKcrdgp-pcJcbG4s220dbVVPjF7ivI-wA,2284
3
3
  coding_cli_runtime/codex_cli.py,sha256=h26tfb1Kj9LyQV19OvMV-DS4NKvdWrDPXVKGhS8lALI,3002
4
4
  coding_cli_runtime/contracts.py,sha256=teYMPDYCjL6HRwRBucJKetfZKlRnxpG82BrC1Y1OMNg,1764
@@ -7,7 +7,7 @@ coding_cli_runtime/copilot_reasoning_logs.py,sha256=S2GD0zGgwVXAPe-DyJPKMR5j-EgO
7
7
  coding_cli_runtime/failure_classification.py,sha256=fjGOjtQaBh6Y13gEIS7ystmadcrFKsLAN-fccBuerBs,6027
8
8
  coding_cli_runtime/headless.py,sha256=0q0L-crpIRb4A7GCOH2kyoSnyw37USGyFTzP6OOQq9Q,4186
9
9
  coding_cli_runtime/json_io.py,sha256=1RseVXV-uPWRm_-pGIUTAvhALnMAEivCX46zvlVpri0,2665
10
- coding_cli_runtime/provider_contracts.py,sha256=IgwpuF5_imwdVARzb2azKsitutahlUTN_KWZxn3Kvw0,15422
10
+ coding_cli_runtime/provider_contracts.py,sha256=-UhDva-gAWLTgk1ZFxo2gKNpb5GL6Cba68yJR9YB47w,20630
11
11
  coding_cli_runtime/provider_controls.py,sha256=2rl1XxGYFODu6LRx3bLhptgN5M_T3klx2lqT_gAAkb0,3328
12
12
  coding_cli_runtime/provider_specs.py,sha256=n9cptkPVI5sIBVuJlqE8nuTU7SyJwG8rkUF2enM_KZc,26777
13
13
  coding_cli_runtime/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
@@ -19,8 +19,8 @@ coding_cli_runtime/session_logs.py,sha256=wyHld9yVydWd06jSxFSW7MwzxSbiJVLNzKN4Hh
19
19
  coding_cli_runtime/subprocess_runner.py,sha256=WqYMI6ALWFhEUySycfHXEtXuCX3m5x7s1n3Bd0TyPm0,11419
20
20
  coding_cli_runtime/schemas/normalized_run_result.v1.json,sha256=ogVKJbDFAd9dJklmp8SUkdR9L5EX1rdHGj5leJJHXGs,1110
21
21
  coding_cli_runtime/schemas/reasoning_metadata.v1.json,sha256=nQWhqp9-dlzJM18OARDUwAyaA-3-I8rETZYvkgTAnOc,467
22
- coding_cli_runtime-0.3.0.dist-info/licenses/LICENSE,sha256=hVIuaMVAgQkhTh44et0cpDtN3kGOZnKQ2bY1rJJw-MI,1078
23
- coding_cli_runtime-0.3.0.dist-info/METADATA,sha256=e9518dIGTaGLay8wou9xEF2V1tjsqqPsZghRLSjIvKs,10801
24
- coding_cli_runtime-0.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
25
- coding_cli_runtime-0.3.0.dist-info/top_level.txt,sha256=-tzjii3Qf_GTevxT5M46tITBY02R-K8Ew04hJRHOB2Y,19
26
- coding_cli_runtime-0.3.0.dist-info/RECORD,,
22
+ coding_cli_runtime-0.4.0.dist-info/licenses/LICENSE,sha256=hVIuaMVAgQkhTh44et0cpDtN3kGOZnKQ2bY1rJJw-MI,1078
23
+ coding_cli_runtime-0.4.0.dist-info/METADATA,sha256=wh9K2YDO1-741eS4nRnSwDY4t0LBpLh9eOb9QtgMzgE,13099
24
+ coding_cli_runtime-0.4.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
25
+ coding_cli_runtime-0.4.0.dist-info/top_level.txt,sha256=-tzjii3Qf_GTevxT5M46tITBY02R-K8Ew04hJRHOB2Y,19
26
+ coding_cli_runtime-0.4.0.dist-info/RECORD,,