Class: Ace::Support::Models::Atoms::ProviderConfigReader

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/support/models/atoms/provider_config_reader.rb

Overview

Reads provider configuration files with cascade support:

  • Project: .ace/llm/providers/
  • User: ~/.ace/llm/providers/
  • Gem: owning packages' .ace-defaults/llm/providers/

Class Method Summary collapse

Class Method Details

.bundled_config(name) ⇒ Object



76
77
78
79
80
81
82
# File 'lib/ace/support/models/atoms/provider_config_reader.rb', line 76

def bundled_config(name)
  bundled_config_directories.each do |dir|
    config = read_file(File.join(dir, "#{name}.yml"))
    return config if config
  end
  nil
end

.bundled_config_directories ⇒ Object

Package defaults are sources, never sync destinations. Discover via gem metadata without loading provider clients or reversing dependencies.



67
68
69
70
71
72
73
74
# File 'lib/ace/support/models/atoms/provider_config_reader.rb', line 67

def bundled_config_directories
  Gem::Specification.latest_specs.filter_map do |spec|
    next unless spec.name.start_with?("ace-")

    path = File.join(spec.full_gem_path, ".ace-defaults", "llm", "providers")
    path if Dir.exist?(path)
  end.sort
end

.config_directories(config_dir: nil) ⇒ Array<String>

Find all provider config directories in cascade order

Parameters:

  • config_dir (String, nil) (defaults to: nil) —

    Override config directory

Returns:

  • (Array<String>) —

    List of directories (project first, then user, then gem)



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/ace/support/models/atoms/provider_config_reader.rb', line 20

def config_directories(config_dir: nil)
  dirs = []

  if config_dir
    dirs << config_dir if Dir.exist?(config_dir)
  else
    # Project-level config
    project_dir = project_config_dir
    dirs << project_dir if project_dir && Dir.exist?(project_dir)

    # User-level config
    user_dir = user_config_dir
    dirs << user_dir if user_dir && Dir.exist?(user_dir)

    # Gem-level config (ace-llm/providers/)
    dirs.concat(bundled_config_directories)
  end

  dirs
end

.extract_last_synced(config) ⇒ Date?

Extract last_synced date from config

Parameters:

  • config (Hash) —

    Provider config

Returns:

  • (Date, nil) —

    Last sync date or nil



161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/ace/support/models/atoms/provider_config_reader.rb', line 161

def extract_last_synced(config)
  value = config["last_synced"]
  return nil unless value

  case value
  when Date
    value
  when String
    Date.parse(value)
  end
rescue ArgumentError
  nil
end

.extract_limits(config) ⇒ Hash

Extract normalized limit configuration from a provider config.

Parameters:

  • config (Hash)

Returns:

  • (Hash) —

    normalized "output"=>..., "models"=>...}



135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/ace/support/models/atoms/provider_config_reader.rb', line 135

def extract_limits(config)
  limits = config["limits"]
  return {} unless limits.is_a?(Hash)

  normalized = {}

  default_limits = normalize_limit_entry(limits["default"])
  normalized["default"] = default_limits if default_limits.any?

  model_limits = normalize_model_limits(limits["models"])
  normalized["models"] = model_limits if model_limits.any?

  normalized
end

.extract_models(config) ⇒ Array<String>

Extract models list from a provider config

Parameters:

  • config (Hash) —

    Provider config

Returns:

  • (Array<String>) —

    List of model IDs



118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/ace/support/models/atoms/provider_config_reader.rb', line 118

def extract_models(config)
  models = config["models"]
  return [] unless models

  case models
  when Array
    models
  when Hash
    models.keys
  else
    []
  end
end

.extract_models_dev_id(config) ⇒ String

Extract models.dev provider ID from config Falls back to provider name if not specified

Parameters:

  • config (Hash) —

    Provider config

Returns:

  • (String) —

    models.dev provider ID



154
155
156
# File 'lib/ace/support/models/atoms/provider_config_reader.rb', line 154

def extract_models_dev_id(config)
  config["models_dev_id"] || config["name"]
end

.read_all(config_dir: nil) ⇒ Hash<String, Hash>

Read all provider configs from cascade

Parameters:

  • config_dir (String, nil) (defaults to: nil) —

    Override config directory

Returns:

  • (Hash<String, Hash>) —

    Provider name => config hash



52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/ace/support/models/atoms/provider_config_reader.rb', line 52

def read_all(config_dir: nil)
  configs = {}

  # Read from all directories (later wins for same provider)
  config_directories(config_dir: config_dir).reverse_each do |dir|
    read_directory(dir).each do |name, config|
      configs[name] = config
    end
  end

  configs
end

.read_directory(dir) ⇒ Hash<String, Hash>

Read provider configs from a specific directory

Parameters:

  • dir (String) —

    Directory path

Returns:

  • (Hash<String, Hash>) —

    Provider name => config hash



87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/ace/support/models/atoms/provider_config_reader.rb', line 87

def read_directory(dir)
  configs = {}

  Dir.glob(File.join(dir, "*.yml")).each do |file|
    name = File.basename(file, ".yml")
    next if name == "template" || name.end_with?(".example")

    config = read_file(file)
    configs[name] = config.merge("_source_file" => file) if config
  end

  configs
end

.read_file(path) ⇒ Hash?

Read a single provider config file

Parameters:

  • path (String) —

    File path

Returns:

  • (Hash, nil) —

    Parsed YAML or nil on error



104
105
106
107
108
109
110
111
112
113
# File 'lib/ace/support/models/atoms/provider_config_reader.rb', line 104

def read_file(path)
  return nil unless File.exist?(path)

  content = File.read(path)
  YAML.safe_load(content, permitted_classes: [Symbol, Date])
rescue Errno::EACCES => e
  raise CacheError, "Permission denied reading #{path}: #{e.message}"
rescue Psych::SyntaxError => e
  raise ConfigError, "Invalid YAML in #{path}: #{e.message}"
end

.writable_config_directory(config_dir: nil) ⇒ String?

Find the first writable config directory

Parameters:

  • config_dir (String, nil) (defaults to: nil) —

    Override config directory

Returns:

  • (String, nil) —

    Writable directory or nil



44
45
46
47
# File 'lib/ace/support/models/atoms/provider_config_reader.rb', line 44

def writable_config_directory(config_dir: nil)
  dirs = config_directories(config_dir: config_dir) - bundled_config_directories
  dirs.find { |dir| writable?(dir) }
end