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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa342251e0d5d1ccc1ee95cb264d88fa552b27b42e8d88d65cc29e06ec18dc93
4
- data.tar.gz: f24622f0ebc04e007f687af00faf50d99d0ced4875969f3d8775ada565515131
3
+ metadata.gz: 48321c690b49c8d136a85e6f131328bbb4a6cfc55e504d1467c56d4d2a554092
4
+ data.tar.gz: 0af124c46785c2278cd5b1d10b4e5fee3b7f67ff6e446cd28e9bbe091abea429
5
5
  SHA512:
6
- metadata.gz: aa3f5270880edd4b6821117530ed34df37e2a65d6a7dafec625a103572b78efc346dfc083eb898ae3af5100beeaf63f97ecad51b93009121ab178e57a3771bc5
7
- data.tar.gz: 2bf24d295ac1b61b1beee10c03c217598dc0f2df1f084c3195851d0bd55aa86e7941e587aefed0a077a52eb92480e5abb22dbd863b1e171c02aaad10d00f815d
6
+ metadata.gz: e13d32931c1e249cba948d8113e124fc7412462fbafd2d397f389c38effe2beadc8f7413ce7e196893680e9c27842cae15218bba6da1db5c3f5e9a085ec34aff
7
+ data.tar.gz: 73b5a3eab3ffeceab03a5c67631162ab245ff512e01262029b253a04bc7acad610926ce4e13c62944f618c9c03b19964ecf0af2ffd9adfec8d7fce23a5bca9f5
@@ -1,5 +1,4 @@
1
1
  timeout: 600
2
2
  cli_args:
3
- - --full-auto
4
3
  - --sandbox
5
4
  - read-only
@@ -1,3 +1,4 @@
1
1
  timeout: 600
2
2
  cli_args:
3
- - --full-auto
3
+ - --sandbox
4
+ - workspace-write
@@ -1 +1,5 @@
1
1
  timeout: 600
2
+ cli_args:
3
+ - --tools
4
+ - read,grep,find,ls
5
+ - --no-extensions
@@ -0,0 +1,3 @@
1
+ cli_args:
2
+ - --thinking
3
+ - high
@@ -0,0 +1,3 @@
1
+ cli_args:
2
+ - --thinking
3
+ - low
@@ -0,0 +1,3 @@
1
+ cli_args:
2
+ - --thinking
3
+ - max
@@ -0,0 +1,3 @@
1
+ cli_args:
2
+ - --thinking
3
+ - medium
@@ -0,0 +1,3 @@
1
+ cli_args:
2
+ - --thinking
3
+ - xhigh
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` (levels: `low`, `medium`, `high`, `xhigh`).
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, :original_input, :role_fallbacks) do
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, original_input)
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
- return client.generate(messages, **generation_opts)
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
- client.generate(messages, **opts)
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]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module LLM
5
- VERSION = "0.39.1"
5
+ VERSION = "0.40.0"
6
6
  end
7
7
  end
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.39.1
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-05 00:00:00.000000000 Z
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