ace-llm-providers-cli 0.31.7 → 0.32.3
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/agy.yml +29 -0
- data/CHANGELOG.md +34 -0
- data/README.md +6 -5
- data/exe/ace-llm-providers-cli-check +5 -2
- data/lib/ace/llm/providers/cli/agy_client.rb +275 -0
- data/lib/ace/llm/providers/cli/atoms/auth_checker.rb +17 -1
- data/lib/ace/llm/providers/cli/molecules/health_checker.rb +9 -0
- data/lib/ace/llm/providers/cli/version.rb +1 -1
- data/lib/ace/llm/providers/cli.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6963c3de4deb7472734fbb346f1daa390982fb8442af80cae5f337dc093deeb
|
|
4
|
+
data.tar.gz: 3b09154bde9e388cf7ef4f52e2ef17c62f93fee36fa5f1fcea7f9a1533cf0521
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c8bd1e4a71471c32f71afc4d0912f446eeafc3eb1bfa73cb9db0f2db87c9fe978326d677c285a4b84aba75019b2655e663ee2de836fbc11c942ba9a3abcb6150
|
|
7
|
+
data.tar.gz: 98a449b5343719f31c42575f2db82881fd7b9efa6c4c7e0bbd3d4e5b8c4ffefe9739f5fcba09b8e79d8d8d417196c925cdc4b4e12c140a69e08547f71cb2f4ca
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: agy
|
|
2
|
+
last_synced: 2026-08-29
|
|
3
|
+
class: Ace::LLM::Providers::CLI::AgyClient
|
|
4
|
+
gem: ace-llm-providers-cli
|
|
5
|
+
models_dev_id: google
|
|
6
|
+
context_limit: 1000000
|
|
7
|
+
models:
|
|
8
|
+
- gemini-3.7-flash-high
|
|
9
|
+
- gemini-3.7-flash-medium
|
|
10
|
+
- gemini-3.6-flash-high
|
|
11
|
+
- gemini-3.6-flash-medium
|
|
12
|
+
- gemini-3.5-flash-medium
|
|
13
|
+
- gemini-3.1-pro-high
|
|
14
|
+
- claude-sonnet-4-6
|
|
15
|
+
aliases:
|
|
16
|
+
global:
|
|
17
|
+
agy: agy:gemini-3.5-flash-medium
|
|
18
|
+
model:
|
|
19
|
+
flash: gemini-3.5-flash-medium
|
|
20
|
+
flash-medium: gemini-3.5-flash-medium
|
|
21
|
+
flash-high: gemini-3.7-flash-high
|
|
22
|
+
pro: gemini-3.1-pro-high
|
|
23
|
+
capabilities:
|
|
24
|
+
- text_generation
|
|
25
|
+
- code_generation
|
|
26
|
+
- streaming
|
|
27
|
+
- function_calling
|
|
28
|
+
default_options:
|
|
29
|
+
temperature: 0.7
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.32.3] - 2026-08-29
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Split `agy` readiness from headless-auth verification so installed, callable Antigravity CLI setups report as ready without being misclassified as blocked on authentication.
|
|
14
|
+
|
|
15
|
+
### Technical
|
|
16
|
+
- Added focused readiness regression coverage and updated the retained CLI smoke scenario to exercise the five-provider surface, including the `agy` ready-but-unverified path.
|
|
17
|
+
|
|
18
|
+
## [0.32.2] - 2026-08-29
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Switched the Antigravity CLI one-shot prompt guard to enforce the configured limit by byte size, preventing multibyte prompts from bypassing the argv launch safety check.
|
|
22
|
+
- Corrected the `agy` readiness check to report installed-but-unverified headless authentication instead of claiming the provider is already authenticated.
|
|
23
|
+
|
|
24
|
+
### Technical
|
|
25
|
+
- Added regression coverage for multibyte prompt-limit enforcement and the updated `agy` readiness semantics.
|
|
26
|
+
|
|
27
|
+
## [0.32.1] - 2026-08-29
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- Rejected oversized Antigravity CLI one-shot prompts with a clear provider error before they can fail at process launch on OS argv-length limits.
|
|
31
|
+
- Treated installed `agy` binaries as configured in readiness checks instead of reporting a hard-coded unauthenticated false negative.
|
|
32
|
+
|
|
33
|
+
### Technical
|
|
34
|
+
- Added regression coverage for the Antigravity oversized-prompt guard and configured-readiness behavior.
|
|
35
|
+
|
|
36
|
+
## [0.32.0] - 2026-08-29
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- Added first-class Antigravity CLI (`agy`) provider support with documented model aliases, one-shot JSON output handling, conversation resume metadata, timeout forwarding, and health-check coverage.
|
|
40
|
+
|
|
41
|
+
### Technical
|
|
42
|
+
- Added retained provider smoke coverage and deterministic registration/command-construction tests for the new Antigravity adapter.
|
|
43
|
+
|
|
10
44
|
## [0.31.7] - 2026-08-12
|
|
11
45
|
|
|
12
46
|
### Technical
|
data/README.md
CHANGED
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
</div>
|
|
14
14
|
|
|
15
|
-
> Works with: Claude Code, Codex CLI, OpenCode, Gemini CLI, pi-agent, and more.
|
|
15
|
+
> Works with: Claude Code, Codex CLI, OpenCode, Gemini CLI, Antigravity CLI, pi-agent, and more.
|
|
16
16
|
|
|
17
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
|
+
`ace-llm-providers-cli` extends [ace-llm](../ace-llm) with provider clients that execute through installed CLI tools (Antigravity, 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.
|
|
20
20
|
|
|
21
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
22
|
|
|
@@ -28,20 +28,21 @@ This package intentionally does not ship a package-local `docs/` directory. Publ
|
|
|
28
28
|
|
|
29
29
|
## Use Cases
|
|
30
30
|
|
|
31
|
-
**Use CLI-native providers through one surface** - run prompts against Claude, Codex, OpenCode, Gemini, pi, and Codex OSS via [ace-llm](../ace-llm) without changing calling conventions.
|
|
31
|
+
**Use CLI-native providers through one surface** - run prompts against Antigravity, Claude, Codex, OpenCode, Gemini, pi, and Codex OSS via [ace-llm](../ace-llm) without changing calling conventions.
|
|
32
32
|
|
|
33
33
|
**Keep provider configuration in versioned YAML** - tune model behavior and provider settings through [ace-support-config](../ace-support-config) instead of custom glue code.
|
|
34
34
|
|
|
35
35
|
**Diagnose local provider readiness** - run `ace-llm-providers-cli-check` to verify that CLI tools are installed, authenticated, and reachable before starting work.
|
|
36
36
|
|
|
37
|
+
**Antigravity contract note** - the `agy` adapter follows Antigravity's documented headless contract as verified on August 29, 2026, including `-p`, `--model`, JSON output, and conversation resume flags forwarded through `cli_args`.
|
|
38
|
+
|
|
37
39
|
## Testing
|
|
38
40
|
|
|
39
41
|
- `ace-test ace-llm-providers-cli` runs deterministic fast coverage from `test/fast/`.
|
|
40
42
|
- `ace-test ace-llm-providers-cli feat` runs deterministic feature coverage from `test/feat/`.
|
|
41
43
|
- `ace-test ace-llm-providers-cli all` runs fast + feat deterministic coverage.
|
|
42
44
|
- `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.
|
|
45
|
+
- E2E provider-discovery scenarios avoid helper interception: the no-tools path uses a baseline PATH (`/usr/bin:/bin`), and success-path stubs are kept as text scripts in a dedicated sandbox stub directory.
|
|
45
46
|
|
|
46
47
|
The package no longer uses legacy `test/integration` naming and does not use
|
|
47
48
|
`ace-test ace-llm-providers-cli e2e` as a deterministic test path.
|
|
@@ -12,6 +12,7 @@ if ARGV.include?("--help") || ARGV.include?("-h") || ARGV.include?("help")
|
|
|
12
12
|
-h, --help Display this help message
|
|
13
13
|
|
|
14
14
|
This utility checks for the following CLI tools:
|
|
15
|
+
- Antigravity (agy)
|
|
15
16
|
- Claude Code (claude)
|
|
16
17
|
- Codex (codex)
|
|
17
18
|
- OpenCode (opencode)
|
|
@@ -36,7 +37,7 @@ max_name_length = results.map { |r| r[:name].length }.max
|
|
|
36
37
|
results.each do |result|
|
|
37
38
|
name_padded = result[:name].ljust(max_name_length)
|
|
38
39
|
status_icon = result[:available] ? "✅" : "❌"
|
|
39
|
-
auth_icon = result[:
|
|
40
|
+
auth_icon = result[:ready] ? "🔓" : "🔒"
|
|
40
41
|
config = result[:config]
|
|
41
42
|
|
|
42
43
|
puts "#{status_icon} #{name_padded} (#{result[:provider]})"
|
|
@@ -56,16 +57,18 @@ end
|
|
|
56
57
|
# Summary
|
|
57
58
|
available_count = results.count { |r| r[:available] }
|
|
58
59
|
authenticated_count = results.count { |r| r[:authenticated] }
|
|
60
|
+
ready_count = results.count { |r| r[:ready] }
|
|
59
61
|
|
|
60
62
|
puts "─" * 50
|
|
61
63
|
puts "\n📊 Summary:"
|
|
62
64
|
puts " Available: #{available_count}/#{results.size} CLI tools installed"
|
|
63
65
|
puts " Authenticated: #{authenticated_count}/#{available_count} tools authenticated" if available_count > 0
|
|
66
|
+
puts " Ready: #{ready_count}/#{available_count} tools ready for use" if available_count > 0
|
|
64
67
|
|
|
65
68
|
if available_count == 0
|
|
66
69
|
puts "\n💡 To use CLI providers, install at least one tool from above."
|
|
67
70
|
puts " After installation, run this check again to verify authentication."
|
|
68
|
-
elsif
|
|
71
|
+
elsif ready_count < available_count
|
|
69
72
|
puts "\n💡 Some tools need authentication. Run the auth commands shown above."
|
|
70
73
|
else
|
|
71
74
|
puts "\n🎉 All installed CLI tools are ready to use with ace-llm!"
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
require_relative "cli_args_support"
|
|
6
|
+
require_relative "atoms/execution_context"
|
|
7
|
+
|
|
8
|
+
module Ace
|
|
9
|
+
module LLM
|
|
10
|
+
module Providers
|
|
11
|
+
module CLI
|
|
12
|
+
# Client for interacting with Antigravity CLI
|
|
13
|
+
class AgyClient < Ace::LLM::Organisms::BaseClient
|
|
14
|
+
include CliArgsSupport
|
|
15
|
+
|
|
16
|
+
API_BASE_URL = "https://antigravity.google"
|
|
17
|
+
DEFAULT_GENERATION_CONFIG = {}.freeze
|
|
18
|
+
DEFAULT_MODEL = "gemini-3.5-flash-medium"
|
|
19
|
+
DEFAULT_MAX_PROMPT_LENGTH = 100_000
|
|
20
|
+
|
|
21
|
+
def initialize(model: nil, **options)
|
|
22
|
+
@model = model || DEFAULT_MODEL
|
|
23
|
+
@options = options
|
|
24
|
+
@generation_config = options[:generation_config] || {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.provider_name
|
|
28
|
+
"agy"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def needs_credentials?
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def list_models
|
|
36
|
+
[
|
|
37
|
+
{id: "gemini-3.7-flash-high", name: "Gemini 3.7 Flash (High)", context_size: 1_000_000},
|
|
38
|
+
{id: "gemini-3.7-flash-medium", name: "Gemini 3.7 Flash (Medium)", context_size: 1_000_000},
|
|
39
|
+
{id: "gemini-3.6-flash-high", name: "Gemini 3.6 Flash (High)", context_size: 1_000_000},
|
|
40
|
+
{id: "gemini-3.6-flash-medium", name: "Gemini 3.6 Flash (Medium)", context_size: 1_000_000},
|
|
41
|
+
{id: "gemini-3.5-flash-medium", name: "Gemini 3.5 Flash (Medium)", context_size: 1_000_000},
|
|
42
|
+
{id: "gemini-3.1-pro-high", name: "Gemini 3.1 Pro (High)", context_size: 1_000_000},
|
|
43
|
+
{id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6 (Thinking)", context_size: 200_000}
|
|
44
|
+
]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def generate(messages, **options)
|
|
48
|
+
validate_agy_availability!
|
|
49
|
+
validate_supported_options!(options)
|
|
50
|
+
|
|
51
|
+
prompt = format_messages_as_prompt(prepare_messages(messages, options))
|
|
52
|
+
prompt = build_full_prompt(prompt, options)
|
|
53
|
+
subprocess_env = options[:subprocess_env]
|
|
54
|
+
working_dir = Atoms::ExecutionContext.resolve_working_dir(
|
|
55
|
+
working_dir: options[:working_dir],
|
|
56
|
+
subprocess_env: subprocess_env
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
cmd = build_agy_command(prompt, options)
|
|
60
|
+
stdout, stderr, status = execute_agy_command(cmd, working_dir: working_dir, options: options)
|
|
61
|
+
|
|
62
|
+
parse_agy_response(stdout, stderr, status, prompt)
|
|
63
|
+
rescue => e
|
|
64
|
+
handle_agy_error(e)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def prepare_messages(messages, options)
|
|
70
|
+
return messages if messages.is_a?(String)
|
|
71
|
+
|
|
72
|
+
process_messages_with_system_append(messages, options[:system_append])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def format_messages_as_prompt(messages)
|
|
76
|
+
return messages if messages.is_a?(String)
|
|
77
|
+
|
|
78
|
+
messages.map do |msg|
|
|
79
|
+
role = msg[:role] || msg["role"]
|
|
80
|
+
content = msg[:content] || msg["content"]
|
|
81
|
+
|
|
82
|
+
case role
|
|
83
|
+
when "system"
|
|
84
|
+
"System: #{content}"
|
|
85
|
+
when "user"
|
|
86
|
+
"User: #{content}"
|
|
87
|
+
when "assistant"
|
|
88
|
+
"Assistant: #{content}"
|
|
89
|
+
else
|
|
90
|
+
content
|
|
91
|
+
end
|
|
92
|
+
end.join("\n\n")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def build_full_prompt(prompt, options)
|
|
96
|
+
prompt_str = prompt.to_s
|
|
97
|
+
return prompt_str if prompt_str.start_with?("System:")
|
|
98
|
+
|
|
99
|
+
system_content = options[:system_instruction] ||
|
|
100
|
+
options[:system] ||
|
|
101
|
+
options[:system_prompt] ||
|
|
102
|
+
@generation_config[:system_prompt]
|
|
103
|
+
|
|
104
|
+
return prompt_str unless system_content
|
|
105
|
+
|
|
106
|
+
"System: #{system_content}\n\n#{prompt_str}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def agy_available?
|
|
110
|
+
system("which", "agy", out: File::NULL, err: File::NULL)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def validate_agy_availability!
|
|
114
|
+
return if agy_available?
|
|
115
|
+
|
|
116
|
+
raise Ace::LLM::ProviderError,
|
|
117
|
+
"Antigravity CLI not found. Install with: curl -fsSL https://antigravity.google/cli/install.sh | bash"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def validate_supported_options!(options)
|
|
121
|
+
unsupported = %i[temperature max_tokens top_p top_k]
|
|
122
|
+
.select { |key| options[key] }
|
|
123
|
+
return if unsupported.empty?
|
|
124
|
+
|
|
125
|
+
raise Ace::LLM::ProviderError,
|
|
126
|
+
"Antigravity CLI does not support #{unsupported.join(", ")} through ace-llm; use documented cli_args instead"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def build_agy_command(prompt, options)
|
|
130
|
+
args = normalized_cli_args(options)
|
|
131
|
+
reject_stream_input_args!(args)
|
|
132
|
+
reject_oversized_prompt!(prompt)
|
|
133
|
+
|
|
134
|
+
cmd = ["agy", "-p", prompt.to_s, "--output-format", "json"]
|
|
135
|
+
cmd << "--model" << @model if @model
|
|
136
|
+
cmd << "--print-timeout" << normalize_print_timeout(options[:timeout]) if options[:timeout]
|
|
137
|
+
cmd << "--sandbox" if options[:sandbox]
|
|
138
|
+
cmd.concat(args)
|
|
139
|
+
cmd
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def reject_stream_input_args!(args)
|
|
143
|
+
flag = find_conflicting_cli_arg(args, ["--input-format"])
|
|
144
|
+
return unless flag
|
|
145
|
+
|
|
146
|
+
raise Ace::LLM::ProviderError,
|
|
147
|
+
"Antigravity one-shot mode does not support cli arg #{flag}; stream-json input requires stdin-driven sessions"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def reject_oversized_prompt!(prompt)
|
|
151
|
+
prompt_bytesize = prompt.to_s.bytesize
|
|
152
|
+
max_length = @generation_config[:max_prompt_length] || @options[:max_prompt_length] || DEFAULT_MAX_PROMPT_LENGTH
|
|
153
|
+
return if prompt_bytesize <= max_length
|
|
154
|
+
|
|
155
|
+
raise Ace::LLM::ProviderError,
|
|
156
|
+
"Antigravity CLI prompt bytesize #{prompt_bytesize} exceeds configured limit #{max_length}. " \
|
|
157
|
+
"Use a provider with file/stdin prompt delivery or lower the prompt size."
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def normalize_print_timeout(timeout)
|
|
161
|
+
value = timeout.to_s
|
|
162
|
+
return "#{value}s" if value.match?(/\A\d+\z/)
|
|
163
|
+
|
|
164
|
+
value
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def execute_agy_command(cmd, working_dir:, options:)
|
|
168
|
+
timeout_val = options[:timeout] || @options[:timeout] || 120
|
|
169
|
+
Molecules::SafeCapture.call(
|
|
170
|
+
cmd,
|
|
171
|
+
timeout: timeout_val,
|
|
172
|
+
stdin_data: "",
|
|
173
|
+
chdir: working_dir,
|
|
174
|
+
env: options[:subprocess_env],
|
|
175
|
+
command_prefix: options[:subprocess_command_prefix],
|
|
176
|
+
provider_name: "Antigravity"
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def parse_agy_response(stdout, stderr, status, prompt)
|
|
181
|
+
response = if stream_json_output?(stdout)
|
|
182
|
+
parse_stream_json(stdout)
|
|
183
|
+
else
|
|
184
|
+
parse_json_or_text(stdout)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
error_message = response["error"].to_s
|
|
188
|
+
run_status = response.fetch("status", status.success? ? "SUCCESS" : "ERROR")
|
|
189
|
+
if !status.success? || run_status != "SUCCESS"
|
|
190
|
+
raise Ace::LLM::ProviderError, build_failure_message(error_message, stderr, stdout, run_status)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
text = extract_response_text(response)
|
|
194
|
+
{
|
|
195
|
+
text: text,
|
|
196
|
+
metadata: build_metadata(response, text, prompt)
|
|
197
|
+
}
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def stream_json_output?(stdout)
|
|
201
|
+
stdout.each_line.lazy.map(&:strip).reject(&:empty?).first.to_s.include?("\"event\"")
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def parse_stream_json(stdout)
|
|
205
|
+
text = +""
|
|
206
|
+
result_payload = nil
|
|
207
|
+
|
|
208
|
+
stdout.each_line do |line|
|
|
209
|
+
line = line.strip
|
|
210
|
+
next if line.empty?
|
|
211
|
+
|
|
212
|
+
entry = JSON.parse(line)
|
|
213
|
+
case entry["event"]
|
|
214
|
+
when "step_update"
|
|
215
|
+
text << entry.dig("step_update", "text_delta").to_s
|
|
216
|
+
when "result"
|
|
217
|
+
result_payload = entry["result"] || {}
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
response = result_payload || {}
|
|
222
|
+
response["response"] = text unless text.empty? || response["response"]
|
|
223
|
+
response
|
|
224
|
+
rescue JSON::ParserError
|
|
225
|
+
{"response" => stdout.to_s.strip}
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def parse_json_or_text(stdout)
|
|
229
|
+
JSON.parse(stdout)
|
|
230
|
+
rescue JSON::ParserError
|
|
231
|
+
{"response" => stdout.to_s.strip}
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def build_failure_message(error_message, stderr, stdout, run_status)
|
|
235
|
+
detail = [error_message, stderr.to_s.strip, stdout.to_s.strip].reject(&:empty?).first
|
|
236
|
+
detail = run_status if detail.nil? || detail.empty?
|
|
237
|
+
"Antigravity CLI failed: #{detail}"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def extract_response_text(response)
|
|
241
|
+
response["response"] || response["text"] || response.dig("result", "response") || ""
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def build_metadata(response, text, prompt)
|
|
245
|
+
usage = response["usage"] || {}
|
|
246
|
+
prompt_tokens = usage["input_tokens"] || (prompt.to_s.length / 4.0).ceil
|
|
247
|
+
output_tokens = usage["output_tokens"] || (text.to_s.length / 4.0).ceil
|
|
248
|
+
conversation_id = response["conversation_id"]
|
|
249
|
+
|
|
250
|
+
metadata = {
|
|
251
|
+
provider: "agy",
|
|
252
|
+
model: @model || DEFAULT_MODEL,
|
|
253
|
+
input_tokens: prompt_tokens,
|
|
254
|
+
output_tokens: output_tokens,
|
|
255
|
+
total_tokens: usage["total_tokens"] || prompt_tokens + output_tokens,
|
|
256
|
+
finish_reason: response["status"]&.downcase || "success",
|
|
257
|
+
timestamp: Time.now.utc.iso8601
|
|
258
|
+
}
|
|
259
|
+
metadata[:thinking_tokens] = usage["thinking_tokens"] if usage["thinking_tokens"]
|
|
260
|
+
metadata[:cache_read_tokens] = usage["cache_read_tokens"] if usage["cache_read_tokens"]
|
|
261
|
+
metadata[:duration_seconds] = response["duration_seconds"] if response["duration_seconds"]
|
|
262
|
+
metadata[:num_turns] = response["num_turns"] if response["num_turns"]
|
|
263
|
+
metadata[:conversation_id] = conversation_id if conversation_id
|
|
264
|
+
metadata[:session_id] = conversation_id if conversation_id
|
|
265
|
+
metadata
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def handle_agy_error(error)
|
|
269
|
+
raise error
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
@@ -11,9 +11,10 @@ module Ace
|
|
|
11
11
|
class AuthChecker
|
|
12
12
|
# Check authentication for a specific provider
|
|
13
13
|
# @param provider [String] Provider name (claude, codex, opencode, codexoss)
|
|
14
|
-
# @return [Hash] Result with :authenticated and :message
|
|
14
|
+
# @return [Hash] Result with :authenticated, :ready, and :message
|
|
15
15
|
def self.check(provider)
|
|
16
16
|
case provider
|
|
17
|
+
when "agy" then check_agy
|
|
17
18
|
when "claude" then check_claude
|
|
18
19
|
when "codex" then check_codex
|
|
19
20
|
when "opencode" then check_opencode
|
|
@@ -34,6 +35,21 @@ module Ace
|
|
|
34
35
|
{authenticated: false, message: "Authentication check failed"}
|
|
35
36
|
end
|
|
36
37
|
|
|
38
|
+
def self.check_agy
|
|
39
|
+
_, _, status = Open3.capture3("agy", "--help")
|
|
40
|
+
if status.success?
|
|
41
|
+
{
|
|
42
|
+
authenticated: false,
|
|
43
|
+
ready: true,
|
|
44
|
+
message: "Installed, but headless authentication is not verified (interactive agy auth may still need to be completed before the first headless run)"
|
|
45
|
+
}
|
|
46
|
+
else
|
|
47
|
+
{authenticated: false, ready: false, message: "Run: agy"}
|
|
48
|
+
end
|
|
49
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
50
|
+
{authenticated: false, ready: false, message: "Authentication check failed"}
|
|
51
|
+
end
|
|
52
|
+
|
|
37
53
|
def self.check_codex
|
|
38
54
|
_, _, status = Open3.capture3("codex", "--help")
|
|
39
55
|
if status.success?
|
|
@@ -11,6 +11,13 @@ module Ace
|
|
|
11
11
|
# Orchestrates provider detection and authentication checking
|
|
12
12
|
class HealthChecker
|
|
13
13
|
PROVIDERS = {
|
|
14
|
+
"agy" => {
|
|
15
|
+
name: "Antigravity",
|
|
16
|
+
provider: "agy",
|
|
17
|
+
check_cmd: ["agy", "--help"],
|
|
18
|
+
install_cmd: "curl -fsSL https://antigravity.google/cli/install.sh | bash",
|
|
19
|
+
install_url: "https://antigravity.google/product/antigravity-cli"
|
|
20
|
+
},
|
|
14
21
|
"claude" => {
|
|
15
22
|
name: "Claude Code",
|
|
16
23
|
provider: "claude",
|
|
@@ -57,6 +64,7 @@ module Ace
|
|
|
57
64
|
config: config,
|
|
58
65
|
available: false,
|
|
59
66
|
authenticated: false,
|
|
67
|
+
ready: false,
|
|
60
68
|
version: nil,
|
|
61
69
|
auth_status: "Not checked"
|
|
62
70
|
}
|
|
@@ -67,6 +75,7 @@ module Ace
|
|
|
67
75
|
|
|
68
76
|
auth_result = Atoms::AuthChecker.check(config[:provider])
|
|
69
77
|
result[:authenticated] = auth_result[:authenticated]
|
|
78
|
+
result[:ready] = auth_result.fetch(:ready, auth_result[:authenticated])
|
|
70
79
|
result[:auth_status] = auth_result[:message]
|
|
71
80
|
end
|
|
72
81
|
|
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.32.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-08-
|
|
10
|
+
date: 2026-08-30 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-llm
|
|
@@ -74,6 +74,7 @@ executables:
|
|
|
74
74
|
extensions: []
|
|
75
75
|
extra_rdoc_files: []
|
|
76
76
|
files:
|
|
77
|
+
- ".ace-defaults/llm/providers/agy.yml"
|
|
77
78
|
- ".ace-defaults/llm/providers/claude.yml"
|
|
78
79
|
- ".ace-defaults/llm/providers/claudeoai.yml"
|
|
79
80
|
- ".ace-defaults/llm/providers/codex.yml"
|
|
@@ -88,6 +89,7 @@ files:
|
|
|
88
89
|
- Rakefile
|
|
89
90
|
- exe/ace-llm-providers-cli-check
|
|
90
91
|
- lib/ace/llm/providers/cli.rb
|
|
92
|
+
- lib/ace/llm/providers/cli/agy_client.rb
|
|
91
93
|
- lib/ace/llm/providers/cli/atoms/args_normalizer.rb
|
|
92
94
|
- lib/ace/llm/providers/cli/atoms/auth_checker.rb
|
|
93
95
|
- lib/ace/llm/providers/cli/atoms/command_formatters.rb
|