ace-llm 0.39.2 → 0.41.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 +4 -4
- data/.ace-defaults/llm/presets/codex/ro.yml +0 -1
- data/.ace-defaults/llm/presets/codex/rw.yml +2 -1
- data/.ace-defaults/llm/presets/pi/ro.yml +4 -0
- data/.ace-defaults/llm/thinking/pi/high.yml +3 -0
- data/.ace-defaults/llm/thinking/pi/low.yml +3 -0
- data/.ace-defaults/llm/thinking/pi/max.yml +3 -0
- data/.ace-defaults/llm/thinking/pi/medium.yml +3 -0
- data/.ace-defaults/llm/thinking/pi/xhigh.yml +3 -0
- data/CHANGELOG.md +17 -0
- data/README.md +2 -2
- data/lib/ace/llm/molecules/fallback_orchestrator.rb +2 -0
- data/lib/ace/llm/molecules/provider_model_parser.rb +1 -1
- data/lib/ace/llm/molecules/role_resolver.rb +1 -1
- data/lib/ace/llm/molecules/thinking_level_loader.rb +1 -1
- data/lib/ace/llm/query_interface.rb +84 -7
- data/lib/ace/llm/version.rb +1 -1
- data/lib/ace/llm.rb +2 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2fae0fd9e7709654101b452e68a39a32412c1788948f60ff6b3ea619ca4877d8
|
|
4
|
+
data.tar.gz: c5ecb76fe5e258e27e5c71ddd8d0f760637ca7a0f67766a7239fe2e60acf3dbf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a885b5933135a969eb4f91a4e013af1e9a35b1e79450c8f47b8ce2b9afa372226c482c1023bab1033438a04b2fc95fbc6ccf4f8592e5b5ce6b763d0820bfb429
|
|
7
|
+
data.tar.gz: b378d04c4280ede3e36d60b01fbe7ee821f28597ac0c9074745961126d642db7e822af19c44f0362d15eaf5067e3ed6b4ee9cf9a2779abf15652e979f9313114
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.41.0] - 2026-09-27
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Query results now derive the top-level `usage` record from provider metadata token counts (model, input/output/cached/total). Absent counts stay absent; a measured zero cache read is kept so cost models can distinguish "no cache" from "unknown". Sessions no longer report "usage unavailable" when the provider supplied tokens.
|
|
15
|
+
|
|
16
|
+
## [0.40.0] - 2026-09-24
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Update Codex CLI presets for the new `codex exec` interface: replace the
|
|
21
|
+
removed `--full-auto` flag with explicit `--sandbox read-only` (ro) and
|
|
22
|
+
`--sandbox workspace-write` (rw); `yolo` keeps
|
|
23
|
+
`--dangerously-bypass-approvals-and-sandbox`.
|
|
24
|
+
- Preserve resolved provider, model, preset and reasoning metadata for the executed request, including fallback targets.
|
|
25
|
+
- Accept `max` reasoning in selectors and Pi reasoning configuration.
|
|
26
|
+
|
|
10
27
|
## [0.39.2] - 2026-09-13
|
|
11
28
|
|
|
12
29
|
### Fixed
|
data/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Deterministic coverage lives in `test/fast/` and `test/feat/`. Scenario assets s
|
|
|
35
35
|
|
|
36
36
|
## How It Works
|
|
37
37
|
|
|
38
|
-
1. Select a model -- by alias, `provider:model`, with a thinking level suffix (`:low`/`:medium`/`:high`), or an `@preset` -- and submit a prompt.
|
|
38
|
+
1. Select a model -- by alias, `provider:model`, with a configured thinking level suffix (`:low`/`:medium`/`:high`/`:xhigh`/`:max`), or an `@preset` -- and submit a prompt.
|
|
39
39
|
2. The provider router resolves the target through [ace-llm-providers-cli](../ace-llm-providers-cli) adapters, applying fallback and retry rules from the [config cascade](../ace-support-config).
|
|
40
40
|
3. The response is returned as text, markdown, or JSON with optional token usage metadata.
|
|
41
41
|
|
|
@@ -43,7 +43,7 @@ Deterministic coverage lives in `test/fast/` and `test/feat/`. Scenario assets s
|
|
|
43
43
|
|
|
44
44
|
**Switch providers with aliases** - use short names like `gflash`, `sonnet`, `opus` instead of full `provider:model` notation. Aliases resolve through versioned YAML in [`.ace-defaults/`](docs/usage.md).
|
|
45
45
|
|
|
46
|
-
**Control reasoning depth** - append a thinking level (`codex:gpt:high`, `claude:sonnet:low`) to tune reasoning budgets. Supported CLI providers: `claude`, `codex` (
|
|
46
|
+
**Control reasoning depth** - append a thinking level (`codex:gpt:high`, `claude:sonnet:low`, `pi:glm5:max`) to tune reasoning budgets. Supported CLI providers: `claude`, `codex` (`low`, `medium`, `high`, `xhigh`) and `pi` (including `max`). Each provider/level pair requires a matching thinking configuration.
|
|
47
47
|
|
|
48
48
|
**Run preset-driven prompts** - apply execution profiles with `@preset` or `--preset`. Built-in presets for CLI providers: `@ro` (read-only), `@rw` (read-write), `@yolo` (full autonomy). Supported by: `claude`, `codex`, `gemini`, `opencode`, `pi`, `agy`.
|
|
49
49
|
|
|
@@ -33,6 +33,7 @@ module Ace
|
|
|
33
33
|
# @raise [Error] If all providers and retries exhausted
|
|
34
34
|
def execute(primary_provider:, registry:)
|
|
35
35
|
@start_time = Time.now
|
|
36
|
+
@primary_provider = primary_provider
|
|
36
37
|
@visited_providers.clear
|
|
37
38
|
|
|
38
39
|
# If fallback disabled, just execute with primary
|
|
@@ -84,6 +85,7 @@ module Ace
|
|
|
84
85
|
client = get_client(provider_name, registry)
|
|
85
86
|
return yield client, provider_name
|
|
86
87
|
rescue => error
|
|
88
|
+
raise if error.is_a?(Ace::LLM::ConfigurationError) && provider_name != @primary_provider
|
|
87
89
|
last_error = error
|
|
88
90
|
|
|
89
91
|
# Handle the error - returns :retry or :stop_and_fallback
|
|
@@ -10,7 +10,7 @@ module Ace
|
|
|
10
10
|
# ProviderModelParser handles parsing and validation of provider:model syntax
|
|
11
11
|
# for the unified LLM query interface.
|
|
12
12
|
class ProviderModelParser
|
|
13
|
-
THINKING_LEVELS = %w[low medium high xhigh].freeze
|
|
13
|
+
THINKING_LEVELS = %w[low medium high xhigh max].freeze
|
|
14
14
|
|
|
15
15
|
# Result object for parsed provider:model combinations.
|
|
16
16
|
ParseResult = Struct.new(:provider, :model, :preset, :thinking_level, :valid, :error,
|
|
@@ -8,7 +8,7 @@ module Ace
|
|
|
8
8
|
module Molecules
|
|
9
9
|
# RoleResolver maps role names to an available concrete selector.
|
|
10
10
|
class RoleResolver
|
|
11
|
-
THINKING_LEVELS = %w[low medium high xhigh].freeze
|
|
11
|
+
THINKING_LEVELS = %w[low medium high xhigh max].freeze
|
|
12
12
|
|
|
13
13
|
def initialize(registry: nil, configuration: nil)
|
|
14
14
|
@registry = registry || ClientRegistry.new
|
|
@@ -8,7 +8,7 @@ module Ace
|
|
|
8
8
|
module Molecules
|
|
9
9
|
# Loads provider-scoped thinking-level overrides from llm/thinking/*.yml.
|
|
10
10
|
class ThinkingLevelLoader
|
|
11
|
-
ALLOWED_LEVELS = %w[low medium high xhigh].freeze
|
|
11
|
+
ALLOWED_LEVELS = %w[low medium high xhigh max].freeze
|
|
12
12
|
|
|
13
13
|
class << self
|
|
14
14
|
def load_for_provider(provider, level)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "shellwords"
|
|
4
|
+
|
|
3
5
|
require_relative "molecules/client_registry"
|
|
4
6
|
require_relative "molecules/provider_model_parser"
|
|
5
7
|
require_relative "molecules/preset_loader"
|
|
@@ -145,11 +147,13 @@ module Ace
|
|
|
145
147
|
|
|
146
148
|
result = {
|
|
147
149
|
text: text_content,
|
|
148
|
-
model: final_model,
|
|
149
|
-
provider: parse_result.provider,
|
|
150
|
-
preset: resolved_preset,
|
|
151
|
-
thinking_level: parse_result.thinking_level,
|
|
152
|
-
|
|
150
|
+
model: response.dig(:execution, :model) || final_model,
|
|
151
|
+
provider: response.dig(:execution, :provider) || parse_result.provider,
|
|
152
|
+
preset: response.fetch(:execution, {}).fetch(:preset, resolved_preset),
|
|
153
|
+
thinking_level: response.fetch(:execution, {}).fetch(:thinking_level, parse_result.thinking_level),
|
|
154
|
+
execution: response[:execution],
|
|
155
|
+
requested_selector: provider_model,
|
|
156
|
+
usage: build_usage(response[:metadata]),
|
|
153
157
|
metadata: response[:metadata]
|
|
154
158
|
}
|
|
155
159
|
|
|
@@ -178,6 +182,27 @@ module Ace
|
|
|
178
182
|
|
|
179
183
|
private
|
|
180
184
|
|
|
185
|
+
# Derive the measured usage record from client metadata. Providers hand
|
|
186
|
+
# token counts to session recording through metadata keys; absent counts
|
|
187
|
+
# stay absent (never zero) so cost models can tell "no cache" from
|
|
188
|
+
# "unknown". Returns nil when the client reported no token counts at all.
|
|
189
|
+
def self.build_usage(metadata)
|
|
190
|
+
meta = metadata.is_a?(Hash) ? metadata : {}
|
|
191
|
+
input = meta[:input_tokens] || meta["input_tokens"]
|
|
192
|
+
output = meta[:output_tokens] || meta["output_tokens"]
|
|
193
|
+
return nil if input.nil? && output.nil?
|
|
194
|
+
|
|
195
|
+
usage = {model: meta[:model] || meta["model"]}
|
|
196
|
+
usage[:input_tokens] = input unless input.nil?
|
|
197
|
+
usage[:output_tokens] = output unless output.nil?
|
|
198
|
+
cached = meta[:cached_tokens] || meta["cached_tokens"]
|
|
199
|
+
usage[:cached_tokens] = cached unless cached.nil?
|
|
200
|
+
total = meta[:total_tokens] || meta["total_tokens"]
|
|
201
|
+
usage[:total_tokens] = total.nil? ? (input.to_i + output.to_i) : total
|
|
202
|
+
usage
|
|
203
|
+
end
|
|
204
|
+
private_class_method :build_usage
|
|
205
|
+
|
|
181
206
|
def self.resolve_preset_name(suffix_preset, explicit_preset)
|
|
182
207
|
suffix = suffix_preset&.to_s&.strip
|
|
183
208
|
explicit = explicit_preset&.to_s&.strip
|
|
@@ -364,7 +389,8 @@ module Ace
|
|
|
364
389
|
registry:, fallback_config:, timeout:, debug:, role_fallbacks: nil, preset: nil, thinking_level: nil, option_builder:)
|
|
365
390
|
if fallback_config.disabled?
|
|
366
391
|
client = registry.get_client(provider, model: model, timeout: timeout)
|
|
367
|
-
|
|
392
|
+
response = client.generate(messages, **generation_opts)
|
|
393
|
+
return response.merge(execution: execution_identity(provider, model, preset, thinking_level, response, generation_opts))
|
|
368
394
|
end
|
|
369
395
|
|
|
370
396
|
primary_provider_string = model ? "#{provider}:#{model}" : provider
|
|
@@ -391,9 +417,60 @@ module Ace
|
|
|
391
417
|
else
|
|
392
418
|
option_builder.call(target_selector)
|
|
393
419
|
end
|
|
394
|
-
|
|
420
|
+
parsed = Molecules::ProviderModelParser.new(registry: registry).parse(target_selector)
|
|
421
|
+
raise ConfigurationError, parsed.error unless parsed.valid?
|
|
422
|
+
primary = target_selector.to_s == primary_provider_string
|
|
423
|
+
response = client.generate(messages, **opts)
|
|
424
|
+
response.merge(execution: execution_identity(
|
|
425
|
+
parsed.provider, parsed.model,
|
|
426
|
+
primary ? preset : (parsed.preset || preset),
|
|
427
|
+
primary ? thinking_level : parsed.thinking_level,
|
|
428
|
+
response,
|
|
429
|
+
opts
|
|
430
|
+
))
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Transport identity records what ACE executed, not a provider attestation.
|
|
435
|
+
def self.execution_identity(provider, model, preset, thinking_level, response, options)
|
|
436
|
+
model ||= response.dig(:metadata, :model) || response.dig(:metadata, "model")
|
|
437
|
+
finish_reason = response.dig(:metadata, :finish_reason) || response.dig(:metadata, "finish_reason") ||
|
|
438
|
+
response.dig(:metadata, :stop_reason) || response.dig(:metadata, "stop_reason")
|
|
439
|
+
status = if Ace::LLM::SUCCESSFUL_FINISH_REASONS.include?(finish_reason.to_s.downcase)
|
|
440
|
+
"succeeded"
|
|
441
|
+
else
|
|
442
|
+
"incomplete"
|
|
443
|
+
end
|
|
444
|
+
{provider: provider, model: model, preset: preset, thinking_level: effective_thinking_level(provider, thinking_level, options),
|
|
445
|
+
identity_source: "resolved_request", status: status}
|
|
446
|
+
end
|
|
447
|
+
private_class_method :execution_identity
|
|
448
|
+
|
|
449
|
+
def self.effective_thinking_level(provider, default, options)
|
|
450
|
+
return default unless %w[pi codex].include?(provider)
|
|
451
|
+
|
|
452
|
+
args = Array(options[:cli_args]).flat_map { |arg| Shellwords.split(arg.to_s) }
|
|
453
|
+
effective = default
|
|
454
|
+
args.each_with_index do |arg, index|
|
|
455
|
+
if provider == "pi"
|
|
456
|
+
candidate = if arg == "--thinking"
|
|
457
|
+
args[index + 1]
|
|
458
|
+
elsif arg.start_with?("--thinking=")
|
|
459
|
+
arg.delete_prefix("--thinking=")
|
|
460
|
+
end
|
|
461
|
+
effective = candidate if %w[off minimal low medium high xhigh max].include?(candidate)
|
|
462
|
+
else
|
|
463
|
+
config = if %w[-c --config].include?(arg)
|
|
464
|
+
args[index + 1]
|
|
465
|
+
elsif arg.match?(/\A(?:-c|--config)=?model_reasoning_effort=/)
|
|
466
|
+
arg.sub(/\A(?:-c|--config)=?/, "")
|
|
467
|
+
end
|
|
468
|
+
effective = config.split("=", 2).last if config&.start_with?("model_reasoning_effort=")
|
|
469
|
+
end
|
|
395
470
|
end
|
|
471
|
+
effective
|
|
396
472
|
end
|
|
473
|
+
private_class_method :effective_thinking_level
|
|
397
474
|
|
|
398
475
|
def self.extract_text_content(response)
|
|
399
476
|
if response[:text]
|
data/lib/ace/llm/version.rb
CHANGED
data/lib/ace/llm.rb
CHANGED
|
@@ -55,6 +55,8 @@ module Ace
|
|
|
55
55
|
class ConfigurationError < Error; end
|
|
56
56
|
class AuthenticationError < Error; end
|
|
57
57
|
|
|
58
|
+
SUCCESSFUL_FINISH_REASONS = %w[success stop end_turn complete completed stop_sequence].freeze
|
|
59
|
+
|
|
58
60
|
# Define module namespaces
|
|
59
61
|
module Atoms; end
|
|
60
62
|
module Molecules; end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-llm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.41.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-09-
|
|
10
|
+
date: 2026-09-27 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-support-cli
|
|
@@ -266,6 +266,11 @@ files:
|
|
|
266
266
|
- ".ace-defaults/llm/thinking/codex/low.yml"
|
|
267
267
|
- ".ace-defaults/llm/thinking/codex/medium.yml"
|
|
268
268
|
- ".ace-defaults/llm/thinking/codex/xhigh.yml"
|
|
269
|
+
- ".ace-defaults/llm/thinking/pi/high.yml"
|
|
270
|
+
- ".ace-defaults/llm/thinking/pi/low.yml"
|
|
271
|
+
- ".ace-defaults/llm/thinking/pi/max.yml"
|
|
272
|
+
- ".ace-defaults/llm/thinking/pi/medium.yml"
|
|
273
|
+
- ".ace-defaults/llm/thinking/pi/xhigh.yml"
|
|
269
274
|
- ".ace-defaults/nav/protocols/guide-sources/ace-llm.yml"
|
|
270
275
|
- CHANGELOG.md
|
|
271
276
|
- LICENSE
|