ace-support-models 0.12.3 → 0.12.4
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/CHANGELOG.md +8 -0
- data/lib/ace/support/models/atoms/provider_catalog_reconciler.rb +54 -0
- data/lib/ace/support/models/atoms/provider_config_reader.rb +22 -33
- data/lib/ace/support/models/atoms/provider_config_writer.rb +19 -0
- data/lib/ace/support/models/cli/commands/providers/sync.rb +7 -3
- data/lib/ace/support/models/molecules/provider_sync_diff.rb +35 -4
- data/lib/ace/support/models/organisms/provider_sync_orchestrator.rb +38 -9
- data/lib/ace/support/models/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0336e5432d5309f300b08f03ff530b23f70a7e5c865aa2eb863d86e7ef87ed99
|
|
4
|
+
data.tar.gz: 75183c42ed84226a56f99e48c5e93accb2a8d4f29fa97c1970aa8374c14fe35d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8509d9953bcae94e7b556fabb82704a5c356a3996c1c8b8eaf63f2433c84514abfe19812244b496b8ef960e9d8221a2f4dc19c0aaf9dad5d354cffd0146df85d
|
|
7
|
+
data.tar.gz: 4c8a078145ceb0f650ae3268ac641f48aa57f2cdf411c18c9db92d505174f2eda9a3b27a4dafccd3403d8a27ea7d2b943e9532ca80feff3b201d54f781571e53
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.12.4] - 2026-09-13
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Preserve Codex local selections, removals, aliases, limits and default order during provider sync; use the bundled native catalog instead of treating models.dev absence as retirement.
|
|
15
|
+
- Discover provider defaults from their owning gems and prevent sync writes into bundled defaults.
|
|
16
|
+
- Persist observed Codex catalog and selection history atomically, keep unknown offers unapplied, and scope sync commits to changed provider files.
|
|
17
|
+
|
|
10
18
|
|
|
11
19
|
## [0.12.3] - 2026-09-02
|
|
12
20
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Ace
|
|
7
|
+
module Support
|
|
8
|
+
module Models
|
|
9
|
+
module Atoms
|
|
10
|
+
# Reconciles an observed local selection with a bundled native catalog.
|
|
11
|
+
# Missing entries on first observation carry no evidence of consent.
|
|
12
|
+
class ProviderCatalogReconciler
|
|
13
|
+
def self.reconcile(config, catalog)
|
|
14
|
+
local = ProviderConfigReader.extract_models(config)
|
|
15
|
+
offered = ProviderConfigReader.extract_models(catalog)
|
|
16
|
+
previous = config["sync_state"]
|
|
17
|
+
previous = nil unless valid_state?(previous)
|
|
18
|
+
removed = previous ? (previous["removed"] + previous["accepted"] - local).uniq : []
|
|
19
|
+
unknown = previous ? previous["unknown"] - local : offered - local
|
|
20
|
+
added = previous ? offered - previous["catalog"] - local - removed - unknown : []
|
|
21
|
+
models = local + added
|
|
22
|
+
state = {
|
|
23
|
+
"catalog" => offered,
|
|
24
|
+
"catalog_digest" => Digest::SHA256.hexdigest(JSON.generate(catalog)),
|
|
25
|
+
"accepted" => models.dup,
|
|
26
|
+
"unknown" => unknown,
|
|
27
|
+
"removed" => removed - local
|
|
28
|
+
}
|
|
29
|
+
desired = config.reject { |key, _| key.start_with?("_source_") }
|
|
30
|
+
# Preserve hash-form local model metadata, as well as order/default.
|
|
31
|
+
desired["models"] = if config["models"].is_a?(Hash)
|
|
32
|
+
config["models"].merge(added.to_h { |id| [id, {}] })
|
|
33
|
+
else
|
|
34
|
+
models
|
|
35
|
+
end
|
|
36
|
+
desired["sync_state"] = state
|
|
37
|
+
{
|
|
38
|
+
config: desired, added: added, offered: (unknown & offered),
|
|
39
|
+
removed: state["removed"], changed: desired != config.reject { |key, _| key.start_with?("_source_") }
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.valid_state?(state)
|
|
44
|
+
state.is_a?(Hash) && state["catalog_digest"].is_a?(String) &&
|
|
45
|
+
%w[catalog accepted unknown removed].all? do |key|
|
|
46
|
+
state[key].is_a?(Array) && state[key].all? { |id| id.is_a?(String) }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
private_class_method :valid_state?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -11,7 +11,7 @@ module Ace
|
|
|
11
11
|
# Reads provider configuration files with cascade support:
|
|
12
12
|
# - Project: .ace/llm/providers/
|
|
13
13
|
# - User: ~/.ace/llm/providers/
|
|
14
|
-
# - Gem: ace-
|
|
14
|
+
# - Gem: owning packages' .ace-defaults/llm/providers/
|
|
15
15
|
class ProviderConfigReader
|
|
16
16
|
class << self
|
|
17
17
|
# Find all provider config directories in cascade order
|
|
@@ -32,8 +32,7 @@ module Ace
|
|
|
32
32
|
dirs << user_dir if user_dir && Dir.exist?(user_dir)
|
|
33
33
|
|
|
34
34
|
# Gem-level config (ace-llm/providers/)
|
|
35
|
-
|
|
36
|
-
dirs << gem_dir if gem_dir && Dir.exist?(gem_dir)
|
|
35
|
+
dirs.concat(bundled_config_directories)
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
dirs
|
|
@@ -43,7 +42,7 @@ module Ace
|
|
|
43
42
|
# @param config_dir [String, nil] Override config directory
|
|
44
43
|
# @return [String, nil] Writable directory or nil
|
|
45
44
|
def writable_config_directory(config_dir: nil)
|
|
46
|
-
dirs = config_directories(config_dir: config_dir)
|
|
45
|
+
dirs = config_directories(config_dir: config_dir) - bundled_config_directories
|
|
47
46
|
dirs.find { |dir| writable?(dir) }
|
|
48
47
|
end
|
|
49
48
|
|
|
@@ -63,6 +62,25 @@ module Ace
|
|
|
63
62
|
configs
|
|
64
63
|
end
|
|
65
64
|
|
|
65
|
+
# Package defaults are sources, never sync destinations. Discover via
|
|
66
|
+
# gem metadata without loading provider clients or reversing dependencies.
|
|
67
|
+
def bundled_config_directories
|
|
68
|
+
Gem::Specification.latest_specs.filter_map do |spec|
|
|
69
|
+
next unless spec.name.start_with?("ace-")
|
|
70
|
+
|
|
71
|
+
path = File.join(spec.full_gem_path, ".ace-defaults", "llm", "providers")
|
|
72
|
+
path if Dir.exist?(path)
|
|
73
|
+
end.sort
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def bundled_config(name)
|
|
77
|
+
bundled_config_directories.each do |dir|
|
|
78
|
+
config = read_file(File.join(dir, "#{name}.yml"))
|
|
79
|
+
return config if config
|
|
80
|
+
end
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
66
84
|
# Read provider configs from a specific directory
|
|
67
85
|
# @param dir [String] Directory path
|
|
68
86
|
# @return [Hash<String, Hash>] Provider name => config hash
|
|
@@ -206,25 +224,6 @@ module Ace
|
|
|
206
224
|
File.join(home, ".ace", "llm", "providers")
|
|
207
225
|
end
|
|
208
226
|
|
|
209
|
-
def gem_config_dir
|
|
210
|
-
# Find ace-llm gem's providers directory (from .ace-defaults/ - single source of truth)
|
|
211
|
-
if defined?(Ace::LLM)
|
|
212
|
-
# Try to find via gem spec
|
|
213
|
-
spec = begin
|
|
214
|
-
Gem::Specification.find_by_name("ace-llm")
|
|
215
|
-
rescue
|
|
216
|
-
nil
|
|
217
|
-
end
|
|
218
|
-
return File.join(spec.gem_dir, ".ace-defaults", "llm", "providers") if spec
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
# Fallback: look relative to this gem in mono-repo development
|
|
222
|
-
# This enables development without installing ace-llm as a gem.
|
|
223
|
-
# Production/installed gem use goes through Gem::Specification above.
|
|
224
|
-
ace_llm_path = find_ace_llm_path
|
|
225
|
-
File.join(ace_llm_path, ".ace-defaults", "llm", "providers") if ace_llm_path
|
|
226
|
-
end
|
|
227
|
-
|
|
228
227
|
def find_project_root_dir
|
|
229
228
|
dir = Dir.pwd
|
|
230
229
|
while dir != "/"
|
|
@@ -236,16 +235,6 @@ module Ace
|
|
|
236
235
|
nil
|
|
237
236
|
end
|
|
238
237
|
|
|
239
|
-
def find_ace_llm_path
|
|
240
|
-
# Look in common locations relative to this gem
|
|
241
|
-
candidates = [
|
|
242
|
-
File.expand_path("../../../../../../ace-llm", __FILE__),
|
|
243
|
-
File.expand_path("../../../../../../../ace-llm", __FILE__)
|
|
244
|
-
]
|
|
245
|
-
|
|
246
|
-
candidates.find { |path| Dir.exist?(path) }
|
|
247
|
-
end
|
|
248
|
-
|
|
249
238
|
def writable?(dir)
|
|
250
239
|
return false unless Dir.exist?(dir)
|
|
251
240
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
require "fileutils"
|
|
5
5
|
require "date"
|
|
6
|
+
require "tempfile"
|
|
6
7
|
|
|
7
8
|
module Ace
|
|
8
9
|
module Support
|
|
@@ -12,6 +13,24 @@ module Ace
|
|
|
12
13
|
# Updates only the models: section while keeping other fields intact
|
|
13
14
|
class ProviderConfigWriter
|
|
14
15
|
class << self
|
|
16
|
+
# Replace config and provenance together; reject edits made after preview.
|
|
17
|
+
def replace(path, config, expected:)
|
|
18
|
+
raise ConfigError, "Config changed during sync: #{path}" unless read_config(path) == expected
|
|
19
|
+
|
|
20
|
+
content = format_config(config)
|
|
21
|
+
Tempfile.create([".provider-sync-", ".yml"], File.dirname(path)) do |file|
|
|
22
|
+
file.chmod(File.stat(path).mode & 0o777)
|
|
23
|
+
file.write(content)
|
|
24
|
+
file.flush
|
|
25
|
+
file.fsync
|
|
26
|
+
raise ConfigError, "Config changed during sync: #{path}" unless read_config(path) == expected
|
|
27
|
+
|
|
28
|
+
backup(path)
|
|
29
|
+
File.rename(file.path, path)
|
|
30
|
+
end
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
15
34
|
# Update the models list in a provider config file
|
|
16
35
|
# @param path [String] Path to config file
|
|
17
36
|
# @param models [Array<String>] New list of model IDs
|
|
@@ -8,13 +8,13 @@ module Ace
|
|
|
8
8
|
module CLI
|
|
9
9
|
module Commands
|
|
10
10
|
module Providers
|
|
11
|
-
# Sync provider YAML configs with models.dev
|
|
11
|
+
# Sync provider YAML configs with native catalogs or models.dev.
|
|
12
12
|
class Sync < Ace::Support::Cli::Command
|
|
13
13
|
include Ace::Support::Cli::Base
|
|
14
14
|
|
|
15
|
-
desc "Sync provider YAML configs with models.dev"
|
|
15
|
+
desc "Sync provider YAML configs with bundled catalogs and models.dev"
|
|
16
16
|
|
|
17
|
-
option :apply, type: :boolean, desc: "Apply changes
|
|
17
|
+
option :apply, type: :boolean, desc: "Apply eligible changes; unresolved Codex offers require explicit config"
|
|
18
18
|
option :commit, type: :boolean, desc: "Commit changes via ace-git-commit"
|
|
19
19
|
option :provider, type: :string, aliases: ["-p"], desc: "Sync specific provider only"
|
|
20
20
|
option :config_dir, type: :string, desc: "Target config directory"
|
|
@@ -47,6 +47,10 @@ module Ace
|
|
|
47
47
|
raise Ace::Support::Cli::Error.new(result[:message])
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
if result[:apply_errors]&.any?
|
|
51
|
+
raise Ace::Support::Cli::Error.new(result[:apply_errors].join("; "))
|
|
52
|
+
end
|
|
53
|
+
|
|
50
54
|
if options[:json]
|
|
51
55
|
puts JSON.pretty_generate(result)
|
|
52
56
|
else
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "date"
|
|
4
|
+
require_relative "../atoms/provider_catalog_reconciler"
|
|
4
5
|
|
|
5
6
|
module Ace
|
|
6
7
|
module Support
|
|
@@ -15,12 +16,13 @@ module Ace
|
|
|
15
16
|
UNCHANGED = :unchanged
|
|
16
17
|
DEPRECATED = :deprecated
|
|
17
18
|
|
|
18
|
-
attr_reader :cache_manager
|
|
19
|
+
attr_reader :cache_manager, :desired_configs
|
|
19
20
|
|
|
20
21
|
# Initialize diff generator
|
|
21
22
|
# @param cache_manager [CacheManager, nil] Cache manager for models.dev data
|
|
22
23
|
def initialize(cache_manager: nil)
|
|
23
24
|
@cache_manager = cache_manager || CacheManager.new
|
|
25
|
+
@desired_configs = {}
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
# Generate diff for all providers
|
|
@@ -30,13 +32,20 @@ module Ace
|
|
|
30
32
|
# @param show_all [Boolean] Show all models regardless of date (ignores since_date)
|
|
31
33
|
# @return [Hash] Diff results by provider
|
|
32
34
|
def generate(current_configs, provider_filter: nil, since_date: nil, show_all: false)
|
|
33
|
-
|
|
35
|
+
selected = current_configs.select { |name, _| !provider_filter || name == provider_filter }
|
|
36
|
+
models_dev_data = (selected.keys.all? { |name| name == "codex" }) ? {} : load_models_dev_data
|
|
37
|
+
@desired_configs = {}
|
|
34
38
|
results = {}
|
|
35
39
|
|
|
36
40
|
current_configs.each do |provider_name, config|
|
|
37
41
|
# Skip if filtering and this isn't the target provider
|
|
38
42
|
next if provider_filter && provider_name != provider_filter
|
|
39
43
|
|
|
44
|
+
if provider_name == "codex"
|
|
45
|
+
results[provider_name] = diff_provider(config, nil, provider_name: provider_name)
|
|
46
|
+
next
|
|
47
|
+
end
|
|
48
|
+
|
|
40
49
|
# Determine the models.dev ID to use (may be mapped via models_dev_id field)
|
|
41
50
|
models_dev_id = Atoms::ProviderConfigReader.extract_models_dev_id(config)
|
|
42
51
|
|
|
@@ -73,6 +82,8 @@ module Ace
|
|
|
73
82
|
# @param provider_name [String, nil] Provider name for canonicalization (e.g., "openrouter")
|
|
74
83
|
# @return [Hash] Diff result
|
|
75
84
|
def diff_provider(config, provider_data, since_date: nil, provider_name: nil)
|
|
85
|
+
return diff_bundled_codex(config) if provider_name == "codex" || config["name"] == "codex"
|
|
86
|
+
|
|
76
87
|
current_models = Set.new(Atoms::ProviderConfigReader.extract_models(config))
|
|
77
88
|
models_dev_models = extract_models_dev_models(provider_data)
|
|
78
89
|
|
|
@@ -162,10 +173,15 @@ module Ace
|
|
|
162
173
|
total_limit_updates = 0
|
|
163
174
|
providers_synced = 0
|
|
164
175
|
providers_skipped = 0
|
|
176
|
+
providers_unresolved = 0
|
|
165
177
|
|
|
166
178
|
results.each do |_provider, result|
|
|
167
179
|
if result[:status] == :ok
|
|
168
|
-
|
|
180
|
+
if Array(result[:offered]).any?
|
|
181
|
+
providers_unresolved += 1
|
|
182
|
+
else
|
|
183
|
+
providers_synced += 1
|
|
184
|
+
end
|
|
169
185
|
total_added += result[:added].size
|
|
170
186
|
total_removed += result[:removed].size
|
|
171
187
|
total_unchanged += result[:unchanged].size
|
|
@@ -183,6 +199,7 @@ module Ace
|
|
|
183
199
|
deprecated: total_deprecated,
|
|
184
200
|
limit_updates: total_limit_updates,
|
|
185
201
|
providers_synced: providers_synced,
|
|
202
|
+
providers_unresolved: providers_unresolved,
|
|
186
203
|
providers_skipped: providers_skipped
|
|
187
204
|
}
|
|
188
205
|
end
|
|
@@ -194,12 +211,26 @@ module Ace
|
|
|
194
211
|
results.any? do |_provider, result|
|
|
195
212
|
next false unless result[:status] == :ok
|
|
196
213
|
|
|
197
|
-
result[:added].any? || result[:removed].any? || result[:limits_changed]
|
|
214
|
+
result[:added].any? || result[:removed].any? || result[:limits_changed] || result[:config_changed]
|
|
198
215
|
end
|
|
199
216
|
end
|
|
200
217
|
|
|
201
218
|
private
|
|
202
219
|
|
|
220
|
+
def diff_bundled_codex(config)
|
|
221
|
+
catalog = Atoms::ProviderConfigReader.bundled_config("codex")
|
|
222
|
+
raise ConfigError, "Bundled Codex catalog not found" unless catalog
|
|
223
|
+
|
|
224
|
+
reconciliation = Atoms::ProviderCatalogReconciler.reconcile(config, catalog)
|
|
225
|
+
@desired_configs["codex"] = reconciliation[:config]
|
|
226
|
+
{
|
|
227
|
+
status: :ok, source: :bundled, added: reconciliation[:added], removed: [], deprecated: [],
|
|
228
|
+
unchanged: Atoms::ProviderConfigReader.extract_models(config),
|
|
229
|
+
offered: reconciliation[:offered], preserved_removals: reconciliation[:removed],
|
|
230
|
+
config_changed: reconciliation[:changed], limits_changed: false
|
|
231
|
+
}
|
|
232
|
+
end
|
|
233
|
+
|
|
203
234
|
def load_models_dev_data
|
|
204
235
|
data = cache_manager.read
|
|
205
236
|
raise CacheError, "No models.dev cache found. Run 'ace-models sync' first." unless data
|
|
@@ -31,11 +31,10 @@ module Ace
|
|
|
31
31
|
# @param since [String, Date, nil] Only show models released after this date
|
|
32
32
|
# @return [Hash] Result with status and details
|
|
33
33
|
def sync(config_dir: nil, provider: nil, apply: false, commit: false, show_all: false, since: nil)
|
|
34
|
-
# Ensure cache is fresh
|
|
35
|
-
ensure_cache_fresh
|
|
36
|
-
|
|
37
34
|
# Read current provider configs
|
|
38
35
|
current_configs = Atoms::ProviderConfigReader.read_all(config_dir: config_dir)
|
|
36
|
+
selected_names = current_configs.keys.select { |name| !provider || name == provider }
|
|
37
|
+
ensure_cache_fresh unless selected_names == ["codex"] || provider == "codex"
|
|
39
38
|
|
|
40
39
|
if current_configs.empty?
|
|
41
40
|
return {
|
|
@@ -75,7 +74,7 @@ module Ace
|
|
|
75
74
|
|
|
76
75
|
# Commit if requested and apply succeeded
|
|
77
76
|
if commit && result[:applied]
|
|
78
|
-
result[:committed] = commit_changes(result[:summary])
|
|
77
|
+
result[:committed] = commit_changes(result[:summary], apply_result[:paths])
|
|
79
78
|
end
|
|
80
79
|
end
|
|
81
80
|
|
|
@@ -87,7 +86,7 @@ module Ace
|
|
|
87
86
|
# @return [String] Formatted output
|
|
88
87
|
def format_result(result)
|
|
89
88
|
lines = []
|
|
90
|
-
lines << "Syncing provider configs with models.dev..."
|
|
89
|
+
lines << "Syncing provider configs with bundled catalogs and models.dev..."
|
|
91
90
|
|
|
92
91
|
# Show date filter info
|
|
93
92
|
if result[:show_all]
|
|
@@ -120,6 +119,10 @@ module Ace
|
|
|
120
119
|
lines << " (#{summary[:providers_skipped]} providers not found in models.dev)"
|
|
121
120
|
end
|
|
122
121
|
|
|
122
|
+
if summary[:providers_unresolved].to_i > 0
|
|
123
|
+
lines << " (#{summary[:providers_unresolved]} providers have unresolved catalog offers)"
|
|
124
|
+
end
|
|
125
|
+
|
|
123
126
|
# Add action hints
|
|
124
127
|
lines << ""
|
|
125
128
|
if result[:changes_detected]
|
|
@@ -133,6 +136,8 @@ module Ace
|
|
|
133
136
|
else
|
|
134
137
|
lines << "Run with --apply to update config files."
|
|
135
138
|
end
|
|
139
|
+
elsif result[:diff].values.any? { |diff| Array(diff[:offered]).any? }
|
|
140
|
+
lines << "Unresolved catalog offers remain; add desired models explicitly to your provider configuration."
|
|
136
141
|
else
|
|
137
142
|
lines << "All providers are up to date."
|
|
138
143
|
end
|
|
@@ -172,10 +177,11 @@ module Ace
|
|
|
172
177
|
def apply_changes(diff_results, current_configs)
|
|
173
178
|
errors = []
|
|
174
179
|
success = true
|
|
180
|
+
paths = []
|
|
175
181
|
|
|
176
182
|
diff_results.each do |provider_name, diff|
|
|
177
183
|
next unless diff[:status] == :ok
|
|
178
|
-
next if diff[:added].empty? && diff[:removed].empty? && !diff[:limits_changed]
|
|
184
|
+
next if diff[:added].empty? && diff[:removed].empty? && !diff[:limits_changed] && !diff[:config_changed]
|
|
179
185
|
|
|
180
186
|
begin
|
|
181
187
|
config = current_configs[provider_name]
|
|
@@ -187,6 +193,20 @@ module Ace
|
|
|
187
193
|
next
|
|
188
194
|
end
|
|
189
195
|
|
|
196
|
+
if Atoms::ProviderConfigReader.bundled_config_directories.include?(File.dirname(source_file))
|
|
197
|
+
raise ConfigError, "Cannot write bundled provider defaults: #{source_file}"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
if diff[:source] == :bundled
|
|
201
|
+
expected = config.reject { |key, _| key.start_with?("_source_") }
|
|
202
|
+
Atoms::ProviderConfigWriter.replace(
|
|
203
|
+
source_file, @diff_generator.desired_configs.fetch(provider_name), expected: expected
|
|
204
|
+
)
|
|
205
|
+
paths << source_file
|
|
206
|
+
output.puts "Updated: #{source_file}"
|
|
207
|
+
next
|
|
208
|
+
end
|
|
209
|
+
|
|
190
210
|
# Calculate new model list
|
|
191
211
|
current_models = Atoms::ProviderConfigReader.extract_models(config)
|
|
192
212
|
new_models = (current_models + diff[:added] - diff[:removed]).uniq.sort
|
|
@@ -201,6 +221,7 @@ module Ace
|
|
|
201
221
|
limits: diff[:desired_limits]
|
|
202
222
|
)
|
|
203
223
|
output.puts "Updated: #{source_file}"
|
|
224
|
+
paths << source_file
|
|
204
225
|
rescue ConfigError => e
|
|
205
226
|
errors << "#{provider_name}: #{e.message}"
|
|
206
227
|
success = false
|
|
@@ -210,17 +231,17 @@ module Ace
|
|
|
210
231
|
end
|
|
211
232
|
end
|
|
212
233
|
|
|
213
|
-
{success: success
|
|
234
|
+
{success: success && paths.any?, errors: errors, paths: paths}
|
|
214
235
|
end
|
|
215
236
|
|
|
216
|
-
def commit_changes(summary)
|
|
237
|
+
def commit_changes(summary, paths)
|
|
217
238
|
message = "chore(providers): Sync model lists with models.dev\n\n" \
|
|
218
239
|
"Added: #{summary[:added]} models\n" \
|
|
219
240
|
"Removed: #{summary[:removed]} models"
|
|
220
241
|
|
|
221
242
|
# Try to use ace-git-commit if available
|
|
222
243
|
begin
|
|
223
|
-
system("ace-git-commit", "-m", message)
|
|
244
|
+
system("ace-git-commit", *paths, "-m", message)
|
|
224
245
|
$?.success?
|
|
225
246
|
rescue Errno::ENOENT
|
|
226
247
|
output.puts "Warning: ace-git-commit not found. Please commit changes manually."
|
|
@@ -230,6 +251,14 @@ module Ace
|
|
|
230
251
|
|
|
231
252
|
def format_provider_diff(provider_name, diff)
|
|
232
253
|
lines = []
|
|
254
|
+
if diff[:source] == :bundled
|
|
255
|
+
lines << "#{provider_name}: (bundled catalog)"
|
|
256
|
+
diff[:added].each { |model| lines << " + #{model} (eligible catalog addition)" }
|
|
257
|
+
diff[:offered].each { |model| lines << " ? #{model} (offer only; explicit configuration required)" }
|
|
258
|
+
diff[:preserved_removals].each { |model| lines << " = #{model} (local removal preserved)" }
|
|
259
|
+
lines << " ~ record observed catalog and selection" if diff[:config_changed]
|
|
260
|
+
return lines.join("\n")
|
|
261
|
+
end
|
|
233
262
|
|
|
234
263
|
# Show models_dev_id mapping if different from provider name
|
|
235
264
|
lines << if diff[:models_dev_id]
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-support-models
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.12.
|
|
4
|
+
version: 0.12.4
|
|
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-13 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-support-core
|
|
@@ -90,6 +90,7 @@ files:
|
|
|
90
90
|
- lib/ace/support/models/atoms/json_parser.rb
|
|
91
91
|
- lib/ace/support/models/atoms/model_filter.rb
|
|
92
92
|
- lib/ace/support/models/atoms/model_name_canonicalizer.rb
|
|
93
|
+
- lib/ace/support/models/atoms/provider_catalog_reconciler.rb
|
|
93
94
|
- lib/ace/support/models/atoms/provider_config_reader.rb
|
|
94
95
|
- lib/ace/support/models/atoms/provider_config_writer.rb
|
|
95
96
|
- lib/ace/support/models/cli.rb
|