ace-llm-providers-cli 0.27.2 → 0.31.6
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/.ace-defaults/llm/providers/claude.yml +10 -6
- data/.ace-defaults/llm/providers/claudeoai.yml +29 -0
- data/.ace-defaults/llm/providers/codex.yml +14 -10
- data/.ace-defaults/llm/providers/codexoai.yml +26 -0
- data/.ace-defaults/llm/providers/gemini.yml +13 -16
- data/.ace-defaults/llm/providers/opencode.yml +43 -11
- data/.ace-defaults/llm/providers/pi.yml +9 -9
- data/CHANGELOG.md +117 -0
- data/README.md +14 -3
- data/lib/ace/llm/providers/cli/atoms/command_formatters.rb +3 -0
- data/lib/ace/llm/providers/cli/atoms/interactive_startup_policy.rb +26 -0
- data/lib/ace/llm/providers/cli/claude_code_client.rb +130 -5
- data/lib/ace/llm/providers/cli/claude_oai_client.rb +4 -2
- data/lib/ace/llm/providers/cli/cli_args_support.rb +15 -0
- data/lib/ace/llm/providers/cli/codex_client.rb +77 -4
- data/lib/ace/llm/providers/cli/codex_oai_client.rb +2 -0
- data/lib/ace/llm/providers/cli/gemini_client.rb +19 -14
- data/lib/ace/llm/providers/cli/molecules/safe_capture.rb +3 -1
- data/lib/ace/llm/providers/cli/open_code_client.rb +2 -0
- data/lib/ace/llm/providers/cli/pi_client.rb +54 -2
- data/lib/ace/llm/providers/cli/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa487689c6778b23ab7c0bd056ed36c60cd9b95716e7399f902b48e437d60c92
|
|
4
|
+
data.tar.gz: 81d2fbb9e87e82383d76b12a40a3dc2d0e747d55fd20b71686e888a7d0567fef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d5a3a738ed6da67fa5224f5227bcd83d9b999bbe63edb5cac436edd2a446a019dbc9b0a59711bfea8d7466ca3f9aa1ff34a4efd0ad7fba355acd4868d3c25756
|
|
7
|
+
data.tar.gz: dfc13de95e231914e3a5b5017e46af8531736b0ea4564bdfbc87ef2ae6c0b3f4c1182c2246933aaeabf2b0d62bf67f0479e5e1821c338e899f76abeed7ed3391
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
name: claude
|
|
2
|
+
last_synced: 2026-04-23
|
|
2
3
|
class: Ace::LLM::Providers::CLI::ClaudeCodeClient
|
|
3
4
|
gem: ace-llm-providers-cli
|
|
5
|
+
models_dev_id: anthropic
|
|
6
|
+
context_limit: 1000000
|
|
4
7
|
models:
|
|
5
|
-
- claude-
|
|
6
|
-
- claude-
|
|
7
|
-
- claude-
|
|
8
|
+
- claude-haiku-4-5
|
|
9
|
+
- claude-opus-4-6
|
|
10
|
+
- claude-opus-4-7
|
|
11
|
+
- claude-sonnet-4-6
|
|
8
12
|
aliases:
|
|
9
13
|
global:
|
|
10
14
|
cc: claude:sonnet
|
|
@@ -12,9 +16,9 @@ aliases:
|
|
|
12
16
|
cc-sonnet: claude:sonnet
|
|
13
17
|
cc-haiku: claude:haiku
|
|
14
18
|
model:
|
|
15
|
-
opus: claude-opus-4-
|
|
16
|
-
sonnet: claude-sonnet-4-
|
|
17
|
-
haiku: claude-
|
|
19
|
+
opus: claude-opus-4-6
|
|
20
|
+
sonnet: claude-sonnet-4-6
|
|
21
|
+
haiku: claude-haiku-4-5
|
|
18
22
|
capabilities:
|
|
19
23
|
- text_generation
|
|
20
24
|
- streaming
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: claudeoai
|
|
2
|
+
class: Ace::LLM::Providers::CLI::ClaudeOaiClient
|
|
3
|
+
gem: ace-llm-providers-cli
|
|
4
|
+
context_limit: 128000
|
|
5
|
+
backends:
|
|
6
|
+
zai:
|
|
7
|
+
base_url: "https://api.z.ai/api/anthropic"
|
|
8
|
+
env_key: "ZAI_API_KEY"
|
|
9
|
+
model_tiers:
|
|
10
|
+
opus: glm-5
|
|
11
|
+
sonnet: glm-5
|
|
12
|
+
haiku: glm-4.7
|
|
13
|
+
models: [glm-5, glm-4.7, glm-4.6]
|
|
14
|
+
models:
|
|
15
|
+
- zai/glm-5
|
|
16
|
+
- zai/glm-4.7
|
|
17
|
+
- zai/glm-4.6
|
|
18
|
+
aliases:
|
|
19
|
+
global:
|
|
20
|
+
claude-oai: claudeoai:zai/glm-5
|
|
21
|
+
model:
|
|
22
|
+
glm: zai/glm-5
|
|
23
|
+
glm47: zai/glm-4.7
|
|
24
|
+
capabilities:
|
|
25
|
+
- text_generation
|
|
26
|
+
- code_generation
|
|
27
|
+
- multi_provider
|
|
28
|
+
default_options:
|
|
29
|
+
temperature: 0.7
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
name: codex
|
|
2
|
+
last_synced: 2026-04-23
|
|
2
3
|
class: Ace::LLM::Providers::CLI::CodexClient
|
|
3
4
|
gem: ace-llm-providers-cli
|
|
5
|
+
models_dev_id: openai
|
|
6
|
+
context_limit: 1050000
|
|
4
7
|
models:
|
|
5
|
-
- gpt-5
|
|
6
|
-
- gpt-5-
|
|
8
|
+
- gpt-5.3-chat-latest
|
|
9
|
+
- gpt-5.3-codex
|
|
10
|
+
- gpt-5.3-codex-spark
|
|
11
|
+
- gpt-5.4
|
|
12
|
+
- gpt-5.4-mini
|
|
13
|
+
- gpt-5.4-nano
|
|
14
|
+
- gpt-5.4-pro
|
|
7
15
|
aliases:
|
|
8
|
-
global:
|
|
9
|
-
codex-gpt5: codex:gpt-5
|
|
10
|
-
codex-mini: codex:gpt-5-mini
|
|
11
16
|
model:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
gpt: gpt-5.4
|
|
18
|
+
codex: gpt-5.3-codex
|
|
19
|
+
spark: gpt-5.3-codex-spark
|
|
20
|
+
mini: gpt-5.4-mini
|
|
15
21
|
capabilities:
|
|
16
22
|
- text_generation
|
|
17
23
|
- code_generation
|
|
18
24
|
- function_calling
|
|
19
|
-
- skill_rewriting
|
|
20
25
|
default_options:
|
|
21
26
|
temperature: 0.7
|
|
22
|
-
skills_dir: .codex/skills
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: codexoai
|
|
2
|
+
class: Ace::LLM::Providers::CLI::CodexOaiClient
|
|
3
|
+
gem: ace-llm-providers-cli
|
|
4
|
+
context_limit: 128000
|
|
5
|
+
backends:
|
|
6
|
+
zai:
|
|
7
|
+
name: "Z.ai GLM"
|
|
8
|
+
base_url: "https://api.z.ai/api/coding/paas/v4"
|
|
9
|
+
env_key: "ZAI_API_KEY"
|
|
10
|
+
models: [glm-5, glm-4.7, glm-4.6]
|
|
11
|
+
models:
|
|
12
|
+
- zai/glm-5
|
|
13
|
+
- zai/glm-4.7
|
|
14
|
+
- zai/glm-4.6
|
|
15
|
+
aliases:
|
|
16
|
+
global:
|
|
17
|
+
codex-oai: codexoai:zai/glm-5
|
|
18
|
+
model:
|
|
19
|
+
glm: zai/glm-5
|
|
20
|
+
glm47: zai/glm-4.7
|
|
21
|
+
capabilities:
|
|
22
|
+
- text_generation
|
|
23
|
+
- code_generation
|
|
24
|
+
- multi_provider
|
|
25
|
+
default_options:
|
|
26
|
+
temperature: 0.7
|
|
@@ -1,32 +1,29 @@
|
|
|
1
1
|
name: gemini
|
|
2
|
+
last_synced: 2026-04-23
|
|
2
3
|
class: Ace::LLM::Providers::CLI::GeminiClient
|
|
3
4
|
gem: ace-llm-providers-cli
|
|
4
5
|
models_dev_id: google
|
|
6
|
+
context_limit: 1000000
|
|
5
7
|
models:
|
|
6
8
|
- gemini-2.5-flash
|
|
7
9
|
- gemini-2.5-pro
|
|
8
|
-
- gemini-
|
|
9
|
-
- gemini-
|
|
10
|
+
- gemini-3-flash-preview
|
|
11
|
+
- gemini-3-pro-preview
|
|
12
|
+
- gemini-3.1-flash-image-preview
|
|
13
|
+
- gemini-3.1-flash-lite-preview
|
|
14
|
+
- gemini-3.1-pro-preview
|
|
15
|
+
- gemini-3.1-pro-preview-customtools
|
|
16
|
+
- gemma-4-26b-a4b-it
|
|
17
|
+
- gemma-4-31b-it
|
|
10
18
|
aliases:
|
|
11
|
-
global:
|
|
12
|
-
gemini-flash: gemini:gemini-2.5-flash
|
|
13
|
-
gemini-pro: gemini:gemini-2.5-pro
|
|
14
|
-
gflash: gemini:gemini-2.5-flash
|
|
15
|
-
gpro: gemini:gemini-2.5-pro
|
|
16
19
|
model:
|
|
17
|
-
2.5-flash: gemini-2.5-flash
|
|
18
|
-
2.5-pro: gemini-2.5-pro
|
|
19
|
-
2.0-flash: gemini-2.0-flash
|
|
20
|
-
1.5-pro: gemini-1.5-pro-latest
|
|
21
20
|
flash: gemini-2.5-flash
|
|
22
|
-
pro: gemini-2.5
|
|
21
|
+
pro: gemini-2.5
|
|
22
|
+
flash-latest: gemini-3-flash-preview
|
|
23
|
+
pro-latest: gemini-3.1-pro-preview
|
|
23
24
|
capabilities:
|
|
24
25
|
- text_generation
|
|
25
26
|
- code_generation
|
|
26
27
|
- vision
|
|
27
28
|
default_options:
|
|
28
29
|
temperature: 0.7
|
|
29
|
-
# Maximum prompt length before switching to file-based prompts (characters)
|
|
30
|
-
# Gemini's actual token limit is ~1M, but this provides a safeguard for CLI usage
|
|
31
|
-
max_prompt_length: 100000
|
|
32
|
-
system_prompt: "Output only the direct answer to stdout. No conversational filler, no acknowledgments, no explanations. Do not attempt to use tools. Be concise and direct."
|
|
@@ -1,21 +1,53 @@
|
|
|
1
1
|
name: opencode
|
|
2
|
+
last_synced: 2026-04-23
|
|
2
3
|
class: Ace::LLM::Providers::CLI::OpenCodeClient
|
|
3
4
|
gem: ace-llm-providers-cli
|
|
5
|
+
context_limit: 128000
|
|
4
6
|
models:
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
- big-pickle
|
|
8
|
+
- claude-haiku-4-5
|
|
9
|
+
- claude-opus-4-5
|
|
10
|
+
- claude-opus-4-6
|
|
11
|
+
- claude-opus-4-7
|
|
12
|
+
- claude-sonnet-4-5
|
|
13
|
+
- claude-sonnet-4-6
|
|
14
|
+
- gemini-3-flash
|
|
15
|
+
- gemini-3-pro
|
|
16
|
+
- gemini-3.1-pro
|
|
17
|
+
- glm-4.6
|
|
18
|
+
- glm-4.7-free
|
|
19
|
+
- glm-5
|
|
20
|
+
- glm-5.1
|
|
21
|
+
- gpt-5.1
|
|
22
|
+
- gpt-5.1-codex
|
|
23
|
+
- gpt-5.1-codex-max
|
|
24
|
+
- gpt-5.2-codex
|
|
25
|
+
- gpt-5.3-codex
|
|
26
|
+
- gpt-5.3-codex-spark
|
|
27
|
+
- gpt-5.4
|
|
28
|
+
- gpt-5.4-mini
|
|
29
|
+
- gpt-5.4-nano
|
|
30
|
+
- gpt-5.4-pro
|
|
31
|
+
- hy3-preview-free
|
|
32
|
+
- kimi-k2
|
|
33
|
+
- kimi-k2-thinking
|
|
34
|
+
- kimi-k2.5
|
|
35
|
+
- kimi-k2.6
|
|
36
|
+
- ling-2.6-flash-free
|
|
37
|
+
- minimax-m2.5
|
|
38
|
+
- minimax-m2.5-free
|
|
39
|
+
- minimax-m2.7
|
|
40
|
+
- nemotron-3-super-free
|
|
41
|
+
- qwen3.5-plus
|
|
42
|
+
- qwen3.6-plus
|
|
12
43
|
aliases:
|
|
13
44
|
global:
|
|
14
|
-
oc: opencode:
|
|
45
|
+
oc: opencode:glm
|
|
15
46
|
model:
|
|
16
|
-
|
|
17
|
-
claude: anthropic/claude-
|
|
18
|
-
|
|
47
|
+
google: google/gemini-3-pro
|
|
48
|
+
claude: anthropic/claude-opus-4-6
|
|
49
|
+
codex: openai/gpt-5.1-codex
|
|
50
|
+
glm: zai-coding-plan/glm-5.1
|
|
19
51
|
capabilities:
|
|
20
52
|
- text_generation
|
|
21
53
|
- streaming
|
|
@@ -4,6 +4,10 @@ gem: ace-llm-providers-cli
|
|
|
4
4
|
models:
|
|
5
5
|
# ZAI (default provider)
|
|
6
6
|
- zai/glm-4.7
|
|
7
|
+
- zai/glm-4.6
|
|
8
|
+
- zai/glm-4.5
|
|
9
|
+
- zai/glm-5.1
|
|
10
|
+
- zai/glm-5-turbo
|
|
7
11
|
# Anthropic
|
|
8
12
|
- anthropic/claude-opus-4-6
|
|
9
13
|
- anthropic/claude-opus-4-5
|
|
@@ -22,15 +26,11 @@ models:
|
|
|
22
26
|
- openai-codex/gpt-5.1
|
|
23
27
|
aliases:
|
|
24
28
|
global:
|
|
25
|
-
pi: pi:zai/glm-
|
|
29
|
+
pi: pi:zai/glm-5.1
|
|
26
30
|
model:
|
|
27
|
-
glm: zai/glm-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
haiku: anthropic/claude-haiku-4-5
|
|
31
|
-
gemini-pro: google-gemini-cli/gemini-2.5-pro
|
|
32
|
-
gemini: google-gemini-cli/gemini-2.5-flash
|
|
33
|
-
gpt: openai-codex/gpt-5.2
|
|
31
|
+
glm: zai/glm-5.1
|
|
32
|
+
glmturbo: zai/glm-5-turbo
|
|
33
|
+
gpt: openai-codex/gpt-5.4
|
|
34
34
|
capabilities:
|
|
35
35
|
- text_generation
|
|
36
36
|
- streaming
|
|
@@ -40,4 +40,4 @@ capabilities:
|
|
|
40
40
|
- skill_rewriting
|
|
41
41
|
default_options:
|
|
42
42
|
temperature: 0.7
|
|
43
|
-
skills_dir: .
|
|
43
|
+
skills_dir: .claude/skills
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,123 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.31.6] - 2026-04-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Always passed the explicit resolved model name to Codex, Gemini, and Claude Code CLI invocations so runtime execution matches config lookup and limit resolution.
|
|
14
|
+
|
|
15
|
+
## [0.31.5] - 2026-04-23
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Refined Codex CLI client startup behavior and startup-policy handling for interactive launches so provider metadata and override paths stay aligned.
|
|
20
|
+
- Updated provider client behavior and tests around interactive policy resolution to keep alias and model mapping stable under tmux launches.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## [0.31.4] - 2026-04-22
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- Replaced tmux Codex HOME overlays with Codex command-line `-C` and trust config overrides so agent sessions keep the user's normal `HOME` for git, GitHub CLI, and other tool authentication.
|
|
27
|
+
|
|
28
|
+
## [0.31.3] - 2026-04-22
|
|
29
|
+
|
|
30
|
+
### Technical
|
|
31
|
+
- Added Codex provider-default regression assertions that explicitly reject stale `gpt-5-mini` model references in shipped model and alias mappings.
|
|
32
|
+
|
|
33
|
+
## [0.31.2] - 2026-04-16
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- Updated shipped Codex defaults from the rejected `gpt-5-mini` alias path to the current `gpt-5.4` / `gpt-5.4-mini` catalog and aligned the CLI client metadata with those model IDs.
|
|
37
|
+
- Added shipped `flash-latest` and `pro-latest` Gemini aliases backed by declared default models so fresh-provider fallback chains no longer target missing Gemini aliases.
|
|
38
|
+
|
|
39
|
+
## [0.31.1] - 2026-04-16
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
- Updated `TS-LLMCLI-001` sandbox setup to source `mise.toml` from `${ACE_E2E_SOURCE_ROOT:-$PROJECT_ROOT_PATH}`, keeping CLI smoke runs compatible with read-only source-root mounts.
|
|
43
|
+
|
|
44
|
+
## [0.31.0] - 2026-04-15
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
- Reworked `TS-LLMCLI-001` to public-surface goal-style guidance by removing hidden `which` interception,
|
|
48
|
+
introducing deterministic sandbox-safe runtime/stub setup, and clarifying provider-discovery harness rules.
|
|
49
|
+
|
|
50
|
+
### Technical
|
|
51
|
+
- Added E2E verification lifecycle metadata guidance (`Last verified`, `Verified by`, refresh trigger) in
|
|
52
|
+
scenario decision/verifier docs.
|
|
53
|
+
- Updated package README testing guidance for deterministic provider-discovery harness behavior.
|
|
54
|
+
|
|
55
|
+
## [0.30.0] - 2026-04-15
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
- Added a Codex tmux startup overlay that trusts the exact working directory through a temporary per-workspace Codex home, preventing fresh tmux sandboxes from blocking on the trust prompt before agent work begins.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
- Switched tmux-facing Codex launches to reuse the shared `ace-llm --interactive` entrypoint, so provider skill translation, alias resolution, preset handling, and startup policy all flow through one path.
|
|
62
|
+
|
|
63
|
+
## [0.29.0] - 2026-04-15
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
- Added native interactive invocation builders for Codex, Claude Code, and pi so CLI providers can be launched without their one-shot print/exec flags while still receiving translated ACE skill handoffs.
|
|
67
|
+
|
|
68
|
+
### Technical
|
|
69
|
+
- Added shared CLI-arg conflict guards plus Claude slash-command formatting support for the new interactive launch path.
|
|
70
|
+
|
|
71
|
+
## [0.28.6] - 2026-04-13
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
- Cleared `CLAUDECODE` during Claude help capability probes so nested Claude Code sessions no longer cache false negatives for `--max-tokens` support.
|
|
75
|
+
|
|
76
|
+
### Technical
|
|
77
|
+
- Added regression coverage for the help-probe environment handling.
|
|
78
|
+
|
|
79
|
+
## [0.28.5] - 2026-04-13
|
|
80
|
+
|
|
81
|
+
### Fixed
|
|
82
|
+
- Avoided eager Claude CLI capability probes during normal command construction so non-`max-tokens` requests no longer shell out to `claude --help`.
|
|
83
|
+
|
|
84
|
+
### Technical
|
|
85
|
+
- Restored deterministic command-builder coverage by guarding common-path unit tests from unexpected capability probing.
|
|
86
|
+
|
|
87
|
+
## [0.28.4] - 2026-04-13
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
- Filter forwarded Claude `--max-tokens` CLI args when the installed Claude CLI does not advertise support, preventing explicit passthrough flags from reintroducing unsupported-option failures.
|
|
91
|
+
|
|
92
|
+
## [0.28.3] - 2026-04-13
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
- Guarded Claude CLI `--max-tokens` usage so commit-generation flows skip the flag on unsupported Claude versions while preserving it when available.
|
|
96
|
+
|
|
97
|
+
### Technical
|
|
98
|
+
- Added command-construction coverage for supported and unsupported Claude `--max-tokens` capability paths.
|
|
99
|
+
|
|
100
|
+
## [0.28.2] - 2026-04-13
|
|
101
|
+
|
|
102
|
+
### Technical
|
|
103
|
+
- Tightened the package version contract test to require a full semantic-version match instead of accepting partial matches.
|
|
104
|
+
## [0.28.1] - 2026-04-13
|
|
105
|
+
|
|
106
|
+
### Changed
|
|
107
|
+
- Completed the batch i05 migration follow-through for this package and aligned it with the restarted `fast` / `feat` / `e2e` verification model.
|
|
108
|
+
|
|
109
|
+
### Technical
|
|
110
|
+
- Included in the coordinated assignment-driven patch release for batch i05 package updates.
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
### Changed
|
|
114
|
+
- Migrated package tests to the `fast` / `feat` / `e2e` model and moved deterministic coverage into `test/fast` and `test/feat`.
|
|
115
|
+
- Updated `TS-LLMCLI-001` scenario metadata and decision records to reference deterministic fast coverage locations.
|
|
116
|
+
- Updated README testing guidance to teach the `ace-test ...`, `ace-test ... feat`, `ace-test ... all`, and `ace-test-e2e ...` contract.
|
|
117
|
+
|
|
118
|
+
## [0.27.3] - 2026-04-10
|
|
119
|
+
|
|
120
|
+
### Fixed
|
|
121
|
+
- Forwarded `subprocess_env` into Codex and Gemini `SafeCapture` execution.
|
|
122
|
+
- Updated Gemini prompt-cache and subprocess working-dir resolution to use execution context directly.
|
|
123
|
+
|
|
124
|
+
### Technical
|
|
125
|
+
- Added Codex and Gemini regression coverage to assert `SafeCapture` receives forwarded `env`.
|
|
126
|
+
|
|
10
127
|
## [0.27.2] - 2026-03-29
|
|
11
128
|
|
|
12
129
|
### Technical
|
data/README.md
CHANGED
|
@@ -14,9 +14,12 @@
|
|
|
14
14
|
|
|
15
15
|
> Works with: Claude Code, Codex CLI, OpenCode, Gemini CLI, pi-agent, and more.
|
|
16
16
|
|
|
17
|
-
[ace-llm Usage Guide](../ace-llm/docs/usage.md) | [ace-llm Handbook](../ace-llm/docs/handbook.md)
|
|
17
|
+
[ace-llm Usage Guide](../ace-llm/docs/usage.md) | [ace-llm Handbook](../ace-llm/docs/handbook.md) | Part of [ACE](https://github.com/cs3b/ace)
|
|
18
|
+
|
|
18
19
|
`ace-llm-providers-cli` extends [ace-llm](../ace-llm) with provider clients that execute through installed CLI tools (Claude, Codex, OpenCode, Gemini, pi, Codex OSS) while preserving the shared command interface. Provider defaults live in versioned YAML, and a health-check command verifies local readiness.
|
|
19
20
|
|
|
21
|
+
This package intentionally does not ship a package-local `docs/` directory. Public usage and operator guidance live in the shared [ace-llm Usage Guide](../ace-llm/docs/usage.md), while `ace-llm-providers-cli-check --help` remains the authoritative runtime help surface for local readiness checks.
|
|
22
|
+
|
|
20
23
|
## How It Works
|
|
21
24
|
|
|
22
25
|
1. The gem registers CLI provider clients on require, making them available to [ace-llm](../ace-llm) automatically.
|
|
@@ -31,6 +34,14 @@
|
|
|
31
34
|
|
|
32
35
|
**Diagnose local provider readiness** - run `ace-llm-providers-cli-check` to verify that CLI tools are installed, authenticated, and reachable before starting work.
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
## Testing
|
|
38
|
+
|
|
39
|
+
- `ace-test ace-llm-providers-cli` runs deterministic fast coverage from `test/fast/`.
|
|
40
|
+
- `ace-test ace-llm-providers-cli feat` runs deterministic feature coverage from `test/feat/`.
|
|
41
|
+
- `ace-test ace-llm-providers-cli all` runs fast + feat deterministic coverage.
|
|
42
|
+
- `ace-test-e2e ace-llm-providers-cli` runs retained workflow scenarios from `test/e2e/`.
|
|
43
|
+
- E2E provider-discovery scenarios avoid helper interception: the no-tools path uses a baseline PATH
|
|
44
|
+
(`/usr/bin:/bin`), and success-path stubs are kept as text scripts in a dedicated sandbox stub directory.
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
The package no longer uses legacy `test/integration` naming and does not use
|
|
47
|
+
`ace-test ace-llm-providers-cli e2e` as a deterministic test path.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module LLM
|
|
5
|
+
module Providers
|
|
6
|
+
module CLI
|
|
7
|
+
module Atoms
|
|
8
|
+
module InteractiveStartupPolicy
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def codex_trust_override(working_dir:)
|
|
12
|
+
path = working_dir.to_s.strip
|
|
13
|
+
return nil if path.empty?
|
|
14
|
+
|
|
15
|
+
%{projects."#{escape_toml_basic_string(path)}".trust_level="trusted"}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def escape_toml_basic_string(value)
|
|
19
|
+
value.to_s.gsub("\\", "\\\\\\\\").gsub("\"", "\\\\\"")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -6,6 +6,9 @@ require "shellwords"
|
|
|
6
6
|
|
|
7
7
|
require_relative "cli_args_support"
|
|
8
8
|
require_relative "atoms/execution_context"
|
|
9
|
+
require_relative "atoms/command_rewriter"
|
|
10
|
+
require_relative "atoms/command_formatters"
|
|
11
|
+
require_relative "molecules/skill_name_reader"
|
|
9
12
|
|
|
10
13
|
module Ace
|
|
11
14
|
module LLM
|
|
@@ -33,6 +36,7 @@ module Ace
|
|
|
33
36
|
# Skip normal BaseClient initialization that requires API key
|
|
34
37
|
@options = options
|
|
35
38
|
@generation_config = options[:generation_config] || {}
|
|
39
|
+
@skill_name_reader = Molecules::SkillNameReader.new
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
# Override to indicate this client doesn't need API credentials
|
|
@@ -60,7 +64,8 @@ module Ace
|
|
|
60
64
|
cmd,
|
|
61
65
|
prompt,
|
|
62
66
|
subprocess_env: subprocess_env,
|
|
63
|
-
working_dir: working_dir
|
|
67
|
+
working_dir: working_dir,
|
|
68
|
+
subprocess_command_prefix: options[:subprocess_command_prefix]
|
|
64
69
|
)
|
|
65
70
|
|
|
66
71
|
parse_claude_response(stdout, stderr, status, prompt, options)
|
|
@@ -79,6 +84,32 @@ module Ace
|
|
|
79
84
|
]
|
|
80
85
|
end
|
|
81
86
|
|
|
87
|
+
def interactive_supported?
|
|
88
|
+
true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def build_interactive_invocation(messages, **options)
|
|
92
|
+
validate_claude_availability!
|
|
93
|
+
|
|
94
|
+
prompt = format_messages_as_prompt(messages)
|
|
95
|
+
subprocess_env = options[:subprocess_env]
|
|
96
|
+
working_dir = Atoms::ExecutionContext.resolve_working_dir(
|
|
97
|
+
working_dir: options[:working_dir],
|
|
98
|
+
subprocess_env: subprocess_env
|
|
99
|
+
)
|
|
100
|
+
prompt = rewrite_skill_commands(prompt, working_dir: working_dir)
|
|
101
|
+
|
|
102
|
+
cmd = build_claude_interactive_command(prompt, options)
|
|
103
|
+
env = {"CLAUDECODE" => nil}
|
|
104
|
+
env.merge!(subprocess_env) if subprocess_env
|
|
105
|
+
{
|
|
106
|
+
command: cmd,
|
|
107
|
+
env: env,
|
|
108
|
+
working_dir: working_dir,
|
|
109
|
+
prompt: prompt
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
|
|
82
113
|
private
|
|
83
114
|
|
|
84
115
|
def format_messages_as_prompt(messages)
|
|
@@ -138,7 +169,7 @@ module Ace
|
|
|
138
169
|
cmd << "--output-format" << "json"
|
|
139
170
|
|
|
140
171
|
# Add model selection if not default
|
|
141
|
-
if @model
|
|
172
|
+
if @model
|
|
142
173
|
cmd << "--model" << @model
|
|
143
174
|
end
|
|
144
175
|
|
|
@@ -146,19 +177,42 @@ module Ace
|
|
|
146
177
|
# (128KB per-argument limit). System content is already embedded in the
|
|
147
178
|
# formatted prompt via format_messages_as_prompt.
|
|
148
179
|
|
|
180
|
+
cli_args = normalized_cli_args(options)
|
|
181
|
+
max_tokens_requested = cli_args_include_max_tokens_flag?(cli_args)
|
|
182
|
+
|
|
149
183
|
# Add max tokens if provided
|
|
150
184
|
max_tokens = options[:max_tokens] || @generation_config[:max_tokens]
|
|
151
185
|
if max_tokens
|
|
152
|
-
|
|
186
|
+
max_tokens_requested = true
|
|
187
|
+
cmd.concat(["--max-tokens", max_tokens.to_s]) if supports_max_tokens_flag?
|
|
153
188
|
end
|
|
154
189
|
|
|
155
190
|
# User CLI args last so they take precedence (last-wins in most CLIs)
|
|
156
|
-
cmd.concat(
|
|
191
|
+
cmd.concat(filter_unsupported_cli_args(cli_args, max_tokens_requested: max_tokens_requested))
|
|
192
|
+
|
|
193
|
+
cmd
|
|
194
|
+
end
|
|
157
195
|
|
|
196
|
+
def build_claude_interactive_command(prompt, options)
|
|
197
|
+
cmd = ["claude"]
|
|
198
|
+
|
|
199
|
+
if @model
|
|
200
|
+
cmd << "--model" << @model
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
cmd.concat(
|
|
204
|
+
normalized_cli_args_without_conflicts(
|
|
205
|
+
options,
|
|
206
|
+
forbidden_flags: ["-p", "--print", "--output-format", "--input-format", "--json-schema",
|
|
207
|
+
"--include-partial-messages", "--include-hook-events", "--no-session-persistence"],
|
|
208
|
+
label: "Claude"
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
cmd << prompt.to_s unless prompt.to_s.empty?
|
|
158
212
|
cmd
|
|
159
213
|
end
|
|
160
214
|
|
|
161
|
-
def execute_claude_command(cmd, prompt, subprocess_env: nil, working_dir: nil)
|
|
215
|
+
def execute_claude_command(cmd, prompt, subprocess_env: nil, working_dir: nil, subprocess_command_prefix: nil)
|
|
162
216
|
timeout_val = @options[:timeout] || 120
|
|
163
217
|
# Clear CLAUDECODE env var so `claude -p` (non-interactive, one-shot mode)
|
|
164
218
|
# can run as a subprocess from within a Claude Code session.
|
|
@@ -173,10 +227,58 @@ module Ace
|
|
|
173
227
|
stdin_data: prompt.to_s,
|
|
174
228
|
chdir: working_dir,
|
|
175
229
|
env: env,
|
|
230
|
+
command_prefix: subprocess_command_prefix,
|
|
176
231
|
provider_name: "Claude"
|
|
177
232
|
)
|
|
178
233
|
end
|
|
179
234
|
|
|
235
|
+
def supports_max_tokens_flag?
|
|
236
|
+
return @supports_max_tokens_flag unless @supports_max_tokens_flag.nil?
|
|
237
|
+
|
|
238
|
+
@supports_max_tokens_flag = command_help_supports_flag?("--max-tokens")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def command_help_supports_flag?(flag)
|
|
242
|
+
[["claude", "-p", "--help"], ["claude", "--help"]].any? do |help_cmd|
|
|
243
|
+
stdout, stderr, status = Open3.capture3({"CLAUDECODE" => nil}, *help_cmd)
|
|
244
|
+
status.success? && [stdout, stderr].join("\n").include?(flag)
|
|
245
|
+
rescue
|
|
246
|
+
false
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def cli_args_include_max_tokens_flag?(cli_args)
|
|
251
|
+
cli_args.each_with_index.any? do |arg, index|
|
|
252
|
+
arg == "--max-tokens" || arg.start_with?("--max-tokens=") || (arg == "--max-tokens" && !cli_args[index + 1].nil?)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def filter_unsupported_cli_args(cli_args, max_tokens_requested: false)
|
|
257
|
+
return cli_args unless max_tokens_requested
|
|
258
|
+
return cli_args if supports_max_tokens_flag?
|
|
259
|
+
|
|
260
|
+
filtered = []
|
|
261
|
+
skip_next = false
|
|
262
|
+
|
|
263
|
+
cli_args.each do |arg|
|
|
264
|
+
if skip_next
|
|
265
|
+
skip_next = false
|
|
266
|
+
next
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
if arg == "--max-tokens"
|
|
270
|
+
skip_next = true
|
|
271
|
+
next
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
next if arg.start_with?("--max-tokens=")
|
|
275
|
+
|
|
276
|
+
filtered << arg
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
filtered
|
|
280
|
+
end
|
|
281
|
+
|
|
180
282
|
def parse_claude_response(stdout, stderr, status, prompt, options)
|
|
181
283
|
unless status.success?
|
|
182
284
|
error_msg = stderr.empty? ? stdout : stderr
|
|
@@ -212,6 +314,29 @@ module Ace
|
|
|
212
314
|
}
|
|
213
315
|
end
|
|
214
316
|
|
|
317
|
+
def rewrite_skill_commands(prompt, working_dir: nil)
|
|
318
|
+
skills_dir = resolve_skills_dir(working_dir: working_dir)
|
|
319
|
+
return prompt unless skills_dir
|
|
320
|
+
|
|
321
|
+
skill_names = @skill_name_reader.call(skills_dir)
|
|
322
|
+
return prompt if skill_names.empty?
|
|
323
|
+
|
|
324
|
+
Atoms::CommandRewriter.call(
|
|
325
|
+
prompt,
|
|
326
|
+
skill_names: skill_names,
|
|
327
|
+
formatter: Atoms::CommandFormatters::CLAUDE_FORMATTER
|
|
328
|
+
)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def resolve_skills_dir(working_dir: nil)
|
|
332
|
+
configured = @options[:skills_dir] || @generation_config[:skills_dir]
|
|
333
|
+
return configured if configured && Dir.exist?(configured)
|
|
334
|
+
|
|
335
|
+
working_dir ||= Atoms::ExecutionContext.resolve_working_dir
|
|
336
|
+
candidate_dir = File.join(working_dir, ".claude", "skills")
|
|
337
|
+
candidate_dir if Dir.exist?(candidate_dir)
|
|
338
|
+
end
|
|
339
|
+
|
|
215
340
|
def extract_claude_text(response)
|
|
216
341
|
direct_result = response["result"]
|
|
217
342
|
direct_response = response["response"]
|
|
@@ -61,7 +61,8 @@ module Ace
|
|
|
61
61
|
cmd,
|
|
62
62
|
prompt,
|
|
63
63
|
subprocess_env: subprocess_env,
|
|
64
|
-
working_dir: working_dir
|
|
64
|
+
working_dir: working_dir,
|
|
65
|
+
subprocess_command_prefix: options[:subprocess_command_prefix]
|
|
65
66
|
)
|
|
66
67
|
|
|
67
68
|
parse_claude_response(stdout, stderr, status, prompt, options)
|
|
@@ -154,7 +155,7 @@ module Ace
|
|
|
154
155
|
cmd
|
|
155
156
|
end
|
|
156
157
|
|
|
157
|
-
def execute_claude_command(cmd, prompt, subprocess_env: nil, working_dir: nil)
|
|
158
|
+
def execute_claude_command(cmd, prompt, subprocess_env: nil, working_dir: nil, subprocess_command_prefix: nil)
|
|
158
159
|
timeout_val = @options[:timeout] || 120
|
|
159
160
|
|
|
160
161
|
# Build env with backend-specific vars for Anthropic-compatible routing
|
|
@@ -169,6 +170,7 @@ module Ace
|
|
|
169
170
|
stdin_data: prompt.to_s,
|
|
170
171
|
chdir: working_dir,
|
|
171
172
|
env: env,
|
|
173
|
+
command_prefix: subprocess_command_prefix,
|
|
172
174
|
provider_name: "Claude OAI"
|
|
173
175
|
)
|
|
174
176
|
end
|
|
@@ -12,6 +12,21 @@ module Ace
|
|
|
12
12
|
def normalized_cli_args(options)
|
|
13
13
|
Atoms::ArgsNormalizer.new.normalize_cli_args(options[:cli_args])
|
|
14
14
|
end
|
|
15
|
+
|
|
16
|
+
def normalized_cli_args_without_conflicts(options, forbidden_flags:, label:)
|
|
17
|
+
args = normalized_cli_args(options)
|
|
18
|
+
conflict = find_conflicting_cli_arg(args, forbidden_flags)
|
|
19
|
+
return args unless conflict
|
|
20
|
+
|
|
21
|
+
raise Ace::LLM::ProviderError, "#{label} interactive mode does not support cli arg #{conflict}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def find_conflicting_cli_arg(args, forbidden_flags)
|
|
25
|
+
normalized_forbidden = Array(forbidden_flags).map(&:to_s)
|
|
26
|
+
args.find do |arg|
|
|
27
|
+
normalized_forbidden.any? { |flag| arg == flag || arg.start_with?("#{flag}=") }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
15
30
|
end
|
|
16
31
|
end
|
|
17
32
|
end
|
|
@@ -8,6 +8,7 @@ require_relative "cli_args_support"
|
|
|
8
8
|
require_relative "atoms/execution_context"
|
|
9
9
|
require_relative "atoms/command_rewriter"
|
|
10
10
|
require_relative "atoms/command_formatters"
|
|
11
|
+
require_relative "atoms/interactive_startup_policy"
|
|
11
12
|
require_relative "atoms/worktree_dir_resolver"
|
|
12
13
|
require_relative "molecules/skill_name_reader"
|
|
13
14
|
|
|
@@ -30,7 +31,7 @@ module Ace
|
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
# Default model (can be overridden by config)
|
|
33
|
-
DEFAULT_MODEL = "gpt-5"
|
|
34
|
+
DEFAULT_MODEL = "gpt-5.4"
|
|
34
35
|
|
|
35
36
|
def initialize(model: nil, **options)
|
|
36
37
|
@model = model || DEFAULT_MODEL
|
|
@@ -74,11 +75,43 @@ module Ace
|
|
|
74
75
|
# Return models based on what the CLI supports
|
|
75
76
|
# Actual models come from YAML config
|
|
76
77
|
[
|
|
77
|
-
{id: "gpt-5", name: "GPT-5", description: "
|
|
78
|
-
{id: "gpt-5-
|
|
78
|
+
{id: "gpt-5.3-codex", name: "GPT-5.3 Codex", description: "Code-specialized Codex model", context_size: 128_000},
|
|
79
|
+
{id: "gpt-5.3-codex-spark", name: "GPT-5.3 Codex Spark", description: "Faster Codex model", context_size: 128_000},
|
|
80
|
+
{id: "gpt-5.4", name: "GPT-5.4", description: "Advanced Codex model", context_size: 128_000},
|
|
81
|
+
{id: "gpt-5.4-mini", name: "GPT-5.4 Mini", description: "Smaller, faster Codex model", context_size: 128_000}
|
|
79
82
|
]
|
|
80
83
|
end
|
|
81
84
|
|
|
85
|
+
def interactive_supported?
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def build_interactive_invocation(messages, **options)
|
|
90
|
+
validate_codex_availability!
|
|
91
|
+
|
|
92
|
+
prompt = format_messages_as_prompt(messages)
|
|
93
|
+
subprocess_env = options[:subprocess_env]
|
|
94
|
+
working_dir = Atoms::ExecutionContext.resolve_working_dir(
|
|
95
|
+
working_dir: options[:working_dir],
|
|
96
|
+
subprocess_env: subprocess_env
|
|
97
|
+
)
|
|
98
|
+
prompt = rewrite_skill_commands(prompt, working_dir: working_dir)
|
|
99
|
+
|
|
100
|
+
env = subprocess_env ? subprocess_env.to_h.dup : {}
|
|
101
|
+
|
|
102
|
+
cmd = build_codex_interactive_command(
|
|
103
|
+
prompt,
|
|
104
|
+
options,
|
|
105
|
+
working_dir: working_dir
|
|
106
|
+
)
|
|
107
|
+
{
|
|
108
|
+
command: cmd,
|
|
109
|
+
env: env,
|
|
110
|
+
working_dir: working_dir,
|
|
111
|
+
prompt: prompt
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
82
115
|
private
|
|
83
116
|
|
|
84
117
|
def format_messages_as_prompt(messages)
|
|
@@ -159,7 +192,7 @@ module Ace
|
|
|
159
192
|
end
|
|
160
193
|
|
|
161
194
|
# Add model selection if not default
|
|
162
|
-
if @model
|
|
195
|
+
if @model
|
|
163
196
|
cmd << "--model" << @model
|
|
164
197
|
end
|
|
165
198
|
|
|
@@ -182,6 +215,44 @@ module Ace
|
|
|
182
215
|
cmd
|
|
183
216
|
end
|
|
184
217
|
|
|
218
|
+
def build_codex_interactive_command(prompt, options, working_dir: nil)
|
|
219
|
+
working_dir ||= Atoms::ExecutionContext.resolve_working_dir(
|
|
220
|
+
working_dir: options[:working_dir],
|
|
221
|
+
subprocess_env: options[:subprocess_env]
|
|
222
|
+
)
|
|
223
|
+
cmd = ["codex"]
|
|
224
|
+
|
|
225
|
+
if options[:sandbox]
|
|
226
|
+
cmd << "--sandbox" << options[:sandbox].to_s
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
if @model
|
|
230
|
+
cmd << "--model" << @model
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
if (git_dir = Atoms::WorktreeDirResolver.call(working_dir: working_dir))
|
|
234
|
+
cmd << "--add-dir" << git_dir
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
cmd << "-C" << working_dir
|
|
238
|
+
|
|
239
|
+
if (trust_override = Atoms::InteractiveStartupPolicy.codex_trust_override(
|
|
240
|
+
working_dir: working_dir
|
|
241
|
+
))
|
|
242
|
+
cmd << "-c" << trust_override
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
cmd.concat(
|
|
246
|
+
normalized_cli_args_without_conflicts(
|
|
247
|
+
options,
|
|
248
|
+
forbidden_flags: ["exec", "review", "-C", "--cd", "--output-last-message"],
|
|
249
|
+
label: "Codex"
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
cmd << prompt.to_s unless prompt.to_s.empty?
|
|
253
|
+
cmd
|
|
254
|
+
end
|
|
255
|
+
|
|
185
256
|
def execute_codex_command(cmd, prompt, options)
|
|
186
257
|
# Prepare the input - combine system prompt with user prompt if needed
|
|
187
258
|
input = prompt.to_s
|
|
@@ -206,6 +277,8 @@ module Ace
|
|
|
206
277
|
timeout: timeout_val,
|
|
207
278
|
stdin_data: input,
|
|
208
279
|
chdir: working_dir,
|
|
280
|
+
env: options[:subprocess_env],
|
|
281
|
+
command_prefix: options[:subprocess_command_prefix],
|
|
209
282
|
provider_name: "Codex"
|
|
210
283
|
)
|
|
211
284
|
end
|
|
@@ -162,7 +162,7 @@ module Ace
|
|
|
162
162
|
cmd << "--output-format" << "json"
|
|
163
163
|
|
|
164
164
|
# Add model selection if not default
|
|
165
|
-
if @model
|
|
165
|
+
if @model
|
|
166
166
|
cmd << "--model" << @model
|
|
167
167
|
end
|
|
168
168
|
|
|
@@ -188,7 +188,7 @@ module Ace
|
|
|
188
188
|
cmd << "--output-format" << "json"
|
|
189
189
|
|
|
190
190
|
# Add model selection if not default
|
|
191
|
-
if @model
|
|
191
|
+
if @model
|
|
192
192
|
cmd << "--model" << @model
|
|
193
193
|
end
|
|
194
194
|
|
|
@@ -232,7 +232,7 @@ module Ace
|
|
|
232
232
|
cmd << "--output-format" << "json"
|
|
233
233
|
|
|
234
234
|
# Add model selection if not default
|
|
235
|
-
if @model
|
|
235
|
+
if @model
|
|
236
236
|
cmd << "--model" << @model
|
|
237
237
|
end
|
|
238
238
|
|
|
@@ -245,7 +245,11 @@ module Ace
|
|
|
245
245
|
end
|
|
246
246
|
|
|
247
247
|
def create_prompt_cache_dir(working_dir = nil, subprocess_env: nil)
|
|
248
|
-
|
|
248
|
+
resolved_working_dir = Atoms::ExecutionContext.resolve_working_dir(
|
|
249
|
+
working_dir: working_dir,
|
|
250
|
+
subprocess_env: subprocess_env
|
|
251
|
+
)
|
|
252
|
+
cache_dir = File.join(resolved_working_dir, ".ace-local", "llm", "prompts")
|
|
249
253
|
FileUtils.mkdir_p(cache_dir) unless Dir.exist?(cache_dir)
|
|
250
254
|
cache_dir
|
|
251
255
|
end
|
|
@@ -256,17 +260,18 @@ module Ace
|
|
|
256
260
|
|
|
257
261
|
def execute_gemini_command(cmd, prompt, options)
|
|
258
262
|
timeout_val = options[:timeout] || @options[:timeout] || 120
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
263
|
+
working_dir = Atoms::ExecutionContext.resolve_working_dir(
|
|
264
|
+
working_dir: options[:working_dir],
|
|
265
|
+
subprocess_env: options[:subprocess_env]
|
|
266
|
+
)
|
|
267
|
+
Molecules::SafeCapture.call(
|
|
268
|
+
cmd,
|
|
269
|
+
timeout: timeout_val,
|
|
270
|
+
chdir: working_dir,
|
|
271
|
+
env: options[:subprocess_env],
|
|
272
|
+
command_prefix: options[:subprocess_command_prefix],
|
|
273
|
+
provider_name: "Gemini"
|
|
268
274
|
)
|
|
269
|
-
Ace::Support::Fs::Molecules::ProjectRootFinder.find(start_path: start_path) || start_path
|
|
270
275
|
end
|
|
271
276
|
|
|
272
277
|
def parse_gemini_response(stdout, stderr, status, prompt, options)
|
|
@@ -27,13 +27,15 @@ module Ace
|
|
|
27
27
|
# @return [Array(String, String, Process::Status)] [stdout, stderr, status]
|
|
28
28
|
# @raise [Ace::LLM::ProviderError] on timeout
|
|
29
29
|
def self.call(cmd, timeout:, stdin_data: nil, chdir: nil, env: nil, provider_name: "CLI",
|
|
30
|
+
command_prefix: nil,
|
|
30
31
|
isolate_process_group: true, cleanup_group_on_exit: true)
|
|
31
32
|
normalized_timeout = normalize_timeout(timeout)
|
|
32
33
|
opts = {}
|
|
33
34
|
opts[:chdir] = chdir if chdir
|
|
34
35
|
opts[:pgroup] = true if isolate_process_group
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
full_cmd = Array(command_prefix) + cmd
|
|
38
|
+
args = env ? [env, *full_cmd] : full_cmd
|
|
37
39
|
|
|
38
40
|
Open3.popen3(*args, **opts) do |stdin, stdout, stderr, wait_thr|
|
|
39
41
|
pid = wait_thr.pid
|
|
@@ -57,7 +57,7 @@ module Ace
|
|
|
57
57
|
full_prompt = rewrite_skill_commands(full_prompt, working_dir: working_dir)
|
|
58
58
|
|
|
59
59
|
cmd = build_pi_command(full_prompt, options, system_prompt: system_prompt)
|
|
60
|
-
stdout, stderr, status = execute_pi_command(cmd, working_dir: working_dir)
|
|
60
|
+
stdout, stderr, status = execute_pi_command(cmd, working_dir: working_dir, options: options)
|
|
61
61
|
|
|
62
62
|
parse_pi_response(stdout, stderr, status, full_prompt, options)
|
|
63
63
|
rescue => e
|
|
@@ -77,6 +77,31 @@ module Ace
|
|
|
77
77
|
]
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
def interactive_supported?
|
|
81
|
+
true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def build_interactive_invocation(messages, **options)
|
|
85
|
+
validate_pi_availability!
|
|
86
|
+
|
|
87
|
+
prompt = format_messages_as_prompt(messages)
|
|
88
|
+
full_prompt, system_prompt = build_full_prompt(prompt, options)
|
|
89
|
+
subprocess_env = options[:subprocess_env]
|
|
90
|
+
working_dir = Atoms::ExecutionContext.resolve_working_dir(
|
|
91
|
+
working_dir: options[:working_dir],
|
|
92
|
+
subprocess_env: subprocess_env
|
|
93
|
+
)
|
|
94
|
+
full_prompt = rewrite_skill_commands(full_prompt, working_dir: working_dir)
|
|
95
|
+
|
|
96
|
+
cmd = build_pi_interactive_command(full_prompt, options, system_prompt: system_prompt)
|
|
97
|
+
{
|
|
98
|
+
command: cmd,
|
|
99
|
+
env: subprocess_env,
|
|
100
|
+
working_dir: working_dir,
|
|
101
|
+
prompt: full_prompt
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
|
|
80
105
|
private
|
|
81
106
|
|
|
82
107
|
def format_messages_as_prompt(messages)
|
|
@@ -178,6 +203,31 @@ module Ace
|
|
|
178
203
|
cmd
|
|
179
204
|
end
|
|
180
205
|
|
|
206
|
+
def build_pi_interactive_command(full_prompt, options, system_prompt: nil)
|
|
207
|
+
cmd = ["pi"]
|
|
208
|
+
|
|
209
|
+
if system_prompt
|
|
210
|
+
cmd << "--system-prompt" << system_prompt
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
model_to_use = @model || @generation_config[:model] || DEFAULT_MODEL
|
|
214
|
+
provider_name, model_id = split_provider_model(model_to_use)
|
|
215
|
+
if provider_name && model_id
|
|
216
|
+
cmd << "--provider" << provider_name
|
|
217
|
+
cmd << "--model" << model_id
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
cmd.concat(
|
|
221
|
+
normalized_cli_args_without_conflicts(
|
|
222
|
+
options,
|
|
223
|
+
forbidden_flags: ["-p", "--print", "--no-session", "--no-skills", "--mode"],
|
|
224
|
+
label: "Pi"
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
cmd << full_prompt.to_s unless full_prompt.to_s.empty?
|
|
228
|
+
cmd
|
|
229
|
+
end
|
|
230
|
+
|
|
181
231
|
# Split "provider/model" into ["provider", "model"]
|
|
182
232
|
# Handles multi-segment providers like "google-gemini-cli/gemini-2.5-pro"
|
|
183
233
|
# Also handles nested providers like "openrouter:openai/gpt-oss-120b"
|
|
@@ -200,13 +250,15 @@ module Ace
|
|
|
200
250
|
[parts[0], parts[1]]
|
|
201
251
|
end
|
|
202
252
|
|
|
203
|
-
def execute_pi_command(cmd, timeout: nil, working_dir: nil)
|
|
253
|
+
def execute_pi_command(cmd, timeout: nil, working_dir: nil, options: {})
|
|
204
254
|
timeout_val = timeout || @options[:timeout] || 120
|
|
205
255
|
Molecules::SafeCapture.call(
|
|
206
256
|
cmd,
|
|
207
257
|
timeout: timeout_val,
|
|
208
258
|
stdin_data: "",
|
|
209
259
|
chdir: working_dir,
|
|
260
|
+
env: options[:subprocess_env],
|
|
261
|
+
command_prefix: options[:subprocess_command_prefix],
|
|
210
262
|
provider_name: "Pi"
|
|
211
263
|
)
|
|
212
264
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-llm-providers-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.31.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-04-26 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-llm
|
|
@@ -75,7 +75,9 @@ extensions: []
|
|
|
75
75
|
extra_rdoc_files: []
|
|
76
76
|
files:
|
|
77
77
|
- ".ace-defaults/llm/providers/claude.yml"
|
|
78
|
+
- ".ace-defaults/llm/providers/claudeoai.yml"
|
|
78
79
|
- ".ace-defaults/llm/providers/codex.yml"
|
|
80
|
+
- ".ace-defaults/llm/providers/codexoai.yml"
|
|
79
81
|
- ".ace-defaults/llm/providers/codexoss.yml"
|
|
80
82
|
- ".ace-defaults/llm/providers/gemini.yml"
|
|
81
83
|
- ".ace-defaults/llm/providers/opencode.yml"
|
|
@@ -91,6 +93,7 @@ files:
|
|
|
91
93
|
- lib/ace/llm/providers/cli/atoms/command_formatters.rb
|
|
92
94
|
- lib/ace/llm/providers/cli/atoms/command_rewriter.rb
|
|
93
95
|
- lib/ace/llm/providers/cli/atoms/execution_context.rb
|
|
96
|
+
- lib/ace/llm/providers/cli/atoms/interactive_startup_policy.rb
|
|
94
97
|
- lib/ace/llm/providers/cli/atoms/provider_detector.rb
|
|
95
98
|
- lib/ace/llm/providers/cli/atoms/session_finders/claude_session_finder.rb
|
|
96
99
|
- lib/ace/llm/providers/cli/atoms/session_finders/codex_session_finder.rb
|