ace-llm 0.39.1 → 0.40.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 +10 -3
- 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 +65 -6
- 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: 48321c690b49c8d136a85e6f131328bbb4a6cfc55e504d1467c56d4d2a554092
|
|
4
|
+
data.tar.gz: 0af124c46785c2278cd5b1d10b4e5fee3b7f67ff6e446cd28e9bbe091abea429
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e13d32931c1e249cba948d8113e124fc7412462fbafd2d397f389c38effe2beadc8f7413ce7e196893680e9c27842cae15218bba6da1db5c3f5e9a085ec34aff
|
|
7
|
+
data.tar.gz: 73b5a3eab3ffeceab03a5c67631162ab245ff512e01262029b253a04bc7acad610926ce4e13c62944f618c9c03b19964ecf0af2ffd9adfec8d7fce23a5bca9f5
|
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.40.0] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Update Codex CLI presets for the new `codex exec` interface: replace the
|
|
15
|
+
removed `--full-auto` flag with explicit `--sandbox read-only` (ro) and
|
|
16
|
+
`--sandbox workspace-write` (rw); `yolo` keeps
|
|
17
|
+
`--dangerously-bypass-approvals-and-sandbox`.
|
|
18
|
+
- Preserve resolved provider, model, preset and reasoning metadata for the executed request, including fallback targets.
|
|
19
|
+
- Accept `max` reasoning in selectors and Pi reasoning configuration.
|
|
20
|
+
|
|
21
|
+
## [0.39.2] - 2026-09-13
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Preserve explicit Codex model targets and native errors without fallback substitution, including Ruby model overrides; retain fallback behavior for aliases, roles and other providers.
|
|
26
|
+
|
|
10
27
|
|
|
11
28
|
## [0.39.1] - 2026-09-02
|
|
12
29
|
|
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,10 +10,11 @@ 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
|
-
ParseResult = Struct.new(:provider, :model, :preset, :thinking_level, :valid, :error,
|
|
16
|
+
ParseResult = Struct.new(:provider, :model, :preset, :thinking_level, :valid, :error,
|
|
17
|
+
:original_input, :role_fallbacks, :explicit_model) do
|
|
17
18
|
def valid?
|
|
18
19
|
valid
|
|
19
20
|
end
|
|
@@ -109,6 +110,11 @@ module Ace
|
|
|
109
110
|
provider_target, preset_name, preset_error = split_preset_suffix(original_input)
|
|
110
111
|
return create_error_result(original_input, preset_error) if preset_error
|
|
111
112
|
|
|
113
|
+
raw_provider, raw_model = provider_target.split(":", 2)
|
|
114
|
+
raw_model, = split_thinking_suffix(raw_model) if raw_model
|
|
115
|
+
raw_selector = "#{raw_provider}:#{raw_model}"
|
|
116
|
+
explicit_model = !raw_model.to_s.empty? && @alias_resolver.resolve(raw_selector) == raw_selector
|
|
117
|
+
|
|
112
118
|
provider_target = @alias_resolver.resolve(provider_target).to_s.strip
|
|
113
119
|
return create_error_result(original_input, "Invalid target: provider/model portion cannot be empty") if provider_target.empty?
|
|
114
120
|
|
|
@@ -146,7 +152,8 @@ module Ace
|
|
|
146
152
|
return create_error_result(original_input, "Invalid target: resolved model cannot be empty")
|
|
147
153
|
end
|
|
148
154
|
|
|
149
|
-
ParseResult.new(resolved_provider, model, preset_name, thinking_level, true, nil,
|
|
155
|
+
ParseResult.new(resolved_provider, model, preset_name, thinking_level, true, nil,
|
|
156
|
+
original_input, nil, explicit_model)
|
|
150
157
|
end
|
|
151
158
|
|
|
152
159
|
def build_role_fallbacks(candidates, caller_preset, caller_thinking)
|
|
@@ -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"
|
|
@@ -90,6 +92,9 @@ module Ace
|
|
|
90
92
|
end
|
|
91
93
|
|
|
92
94
|
fallback_config = load_fallback_config(fallback, fallback_providers, parser: parser)
|
|
95
|
+
if parse_result.provider == "codex" && (model || parse_result.explicit_model)
|
|
96
|
+
fallback_config = fallback_config.merge(enabled: false)
|
|
97
|
+
end
|
|
93
98
|
timeout_value = first_non_nil(timeout, execution_overrides["timeout"], Molecules::ConfigLoader.get("llm.timeout"), 120)
|
|
94
99
|
resolved_timeout = normalize_timeout(timeout_value)
|
|
95
100
|
parser_for_options = Molecules::ProviderModelParser.new(registry: registry)
|
|
@@ -142,10 +147,12 @@ module Ace
|
|
|
142
147
|
|
|
143
148
|
result = {
|
|
144
149
|
text: text_content,
|
|
145
|
-
model: final_model,
|
|
146
|
-
provider: parse_result.provider,
|
|
147
|
-
preset: resolved_preset,
|
|
148
|
-
thinking_level: parse_result.thinking_level,
|
|
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,
|
|
149
156
|
usage: response[:usage],
|
|
150
157
|
metadata: response[:metadata]
|
|
151
158
|
}
|
|
@@ -361,7 +368,8 @@ module Ace
|
|
|
361
368
|
registry:, fallback_config:, timeout:, debug:, role_fallbacks: nil, preset: nil, thinking_level: nil, option_builder:)
|
|
362
369
|
if fallback_config.disabled?
|
|
363
370
|
client = registry.get_client(provider, model: model, timeout: timeout)
|
|
364
|
-
|
|
371
|
+
response = client.generate(messages, **generation_opts)
|
|
372
|
+
return response.merge(execution: execution_identity(provider, model, preset, thinking_level, response, generation_opts))
|
|
365
373
|
end
|
|
366
374
|
|
|
367
375
|
primary_provider_string = model ? "#{provider}:#{model}" : provider
|
|
@@ -388,9 +396,60 @@ module Ace
|
|
|
388
396
|
else
|
|
389
397
|
option_builder.call(target_selector)
|
|
390
398
|
end
|
|
391
|
-
|
|
399
|
+
parsed = Molecules::ProviderModelParser.new(registry: registry).parse(target_selector)
|
|
400
|
+
raise ConfigurationError, parsed.error unless parsed.valid?
|
|
401
|
+
primary = target_selector.to_s == primary_provider_string
|
|
402
|
+
response = client.generate(messages, **opts)
|
|
403
|
+
response.merge(execution: execution_identity(
|
|
404
|
+
parsed.provider, parsed.model,
|
|
405
|
+
primary ? preset : (parsed.preset || preset),
|
|
406
|
+
primary ? thinking_level : parsed.thinking_level,
|
|
407
|
+
response,
|
|
408
|
+
opts
|
|
409
|
+
))
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Transport identity records what ACE executed, not a provider attestation.
|
|
414
|
+
def self.execution_identity(provider, model, preset, thinking_level, response, options)
|
|
415
|
+
model ||= response.dig(:metadata, :model) || response.dig(:metadata, "model")
|
|
416
|
+
finish_reason = response.dig(:metadata, :finish_reason) || response.dig(:metadata, "finish_reason") ||
|
|
417
|
+
response.dig(:metadata, :stop_reason) || response.dig(:metadata, "stop_reason")
|
|
418
|
+
status = if Ace::LLM::SUCCESSFUL_FINISH_REASONS.include?(finish_reason.to_s.downcase)
|
|
419
|
+
"succeeded"
|
|
420
|
+
else
|
|
421
|
+
"incomplete"
|
|
422
|
+
end
|
|
423
|
+
{provider: provider, model: model, preset: preset, thinking_level: effective_thinking_level(provider, thinking_level, options),
|
|
424
|
+
identity_source: "resolved_request", status: status}
|
|
425
|
+
end
|
|
426
|
+
private_class_method :execution_identity
|
|
427
|
+
|
|
428
|
+
def self.effective_thinking_level(provider, default, options)
|
|
429
|
+
return default unless %w[pi codex].include?(provider)
|
|
430
|
+
|
|
431
|
+
args = Array(options[:cli_args]).flat_map { |arg| Shellwords.split(arg.to_s) }
|
|
432
|
+
effective = default
|
|
433
|
+
args.each_with_index do |arg, index|
|
|
434
|
+
if provider == "pi"
|
|
435
|
+
candidate = if arg == "--thinking"
|
|
436
|
+
args[index + 1]
|
|
437
|
+
elsif arg.start_with?("--thinking=")
|
|
438
|
+
arg.delete_prefix("--thinking=")
|
|
439
|
+
end
|
|
440
|
+
effective = candidate if %w[off minimal low medium high xhigh max].include?(candidate)
|
|
441
|
+
else
|
|
442
|
+
config = if %w[-c --config].include?(arg)
|
|
443
|
+
args[index + 1]
|
|
444
|
+
elsif arg.match?(/\A(?:-c|--config)=?model_reasoning_effort=/)
|
|
445
|
+
arg.sub(/\A(?:-c|--config)=?/, "")
|
|
446
|
+
end
|
|
447
|
+
effective = config.split("=", 2).last if config&.start_with?("model_reasoning_effort=")
|
|
448
|
+
end
|
|
392
449
|
end
|
|
450
|
+
effective
|
|
393
451
|
end
|
|
452
|
+
private_class_method :effective_thinking_level
|
|
394
453
|
|
|
395
454
|
def self.extract_text_content(response)
|
|
396
455
|
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.40.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-24 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
|