Class: Clacky::AgentConfig
- Inherits:
-
Object
- Object
- Clacky::AgentConfig
- Defined in:
- lib/clacky/agent_config.rb
Constant Summary collapse
- CONFIG_DIR =
File.join(Dir.home, ".clacky")
- CONFIG_FILE =
File.join(CONFIG_DIR, "config.yml")
- CLAUDE_DEFAULT_MODEL =
Default model for ClaudeCode environment
"claude-sonnet-4-5"- PERMISSION_MODES =
[:auto_approve, :confirm_safes, :confirm_all].freeze
- FALLBACK_COOLING_OFF_SECONDS =
How long to stay on the fallback model before probing the primary again.
30 * 60
Instance Attribute Summary collapse
-
#current_model_index ⇒ Object
Returns the value of attribute current_model_index.
-
#enable_compression ⇒ Object
Returns the value of attribute enable_compression.
-
#enable_prompt_caching ⇒ Object
Returns the value of attribute enable_prompt_caching.
-
#max_tokens ⇒ Object
Returns the value of attribute max_tokens.
-
#memory_update_enabled ⇒ Object
Returns the value of attribute memory_update_enabled.
-
#models ⇒ Object
Returns the value of attribute models.
-
#permission_mode ⇒ Object
Returns the value of attribute permission_mode.
-
#skill_evolution ⇒ Object
Returns the value of attribute skill_evolution.
-
#verbose ⇒ Object
Returns the value of attribute verbose.
Class Method Summary collapse
-
.load(config_file = CONFIG_FILE) ⇒ Object
Load configuration from file.
Instance Method Summary collapse
-
#activate_fallback!(fallback_model_name) ⇒ Object
Switch to fallback model and start the cooling-off clock.
-
#add_model(model:, api_key:, base_url:, anthropic_format: false, type: nil) ⇒ Object
Add a new model configuration.
-
#anthropic_format? ⇒ Boolean
Check if should use Anthropic format for current model.
-
#api_key ⇒ Object
Get API key for current model.
-
#api_key=(value) ⇒ Object
Set API key for current model.
-
#base_url ⇒ Object
Get base URL for current model.
-
#base_url=(value) ⇒ Object
Set base URL for current model.
-
#bedrock? ⇒ Boolean
Check if current model uses Bedrock Converse API (ABSK key prefix or abs- model prefix).
-
#confirm_fallback_ok! ⇒ Object
Called when a successful API response is received.
-
#current_model ⇒ Object
Get current model configuration Looks for type: default first, falls back to current_model_index.
-
#deep_copy ⇒ Object
Save configuration to file Deep copy — models array contains mutable Hashes, so a shallow dup would let the copy share the same Hash objects with the original, causing Settings changes to silently mutate already-running session configs.
-
#default_model ⇒ Object
Get the default model (type: default) Falls back to current_model for backward compatibility.
-
#effective_model_name ⇒ Object
The effective model name to use for API calls.
-
#fallback_active? ⇒ Boolean
Returns true when a fallback model is currently being used (:fallback_active or :probing states).
-
#fallback_model_for(model_name) ⇒ String?
Look up the fallback model name for the given model name.
-
#find_model_by_type(type) ⇒ Object
Find model by type (default or lite) Returns the model hash or nil if not found.
-
#get_model(index) ⇒ Object
Get model by index.
-
#initialize(options = {}) ⇒ AgentConfig
constructor
A new instance of AgentConfig.
-
#lite_model ⇒ Object
Get the lite model (type: lite) Returns nil if no lite model configured.
-
#maybe_start_probing ⇒ Object
Called at the start of every call_llm.
-
#model_name ⇒ Object
Get model name for current model.
-
#model_name=(value) ⇒ Object
Set model name for current model.
-
#model_names ⇒ Object
List all model names.
-
#models_configured? ⇒ Boolean
Check if any model is configured.
-
#probing? ⇒ Boolean
Returns true only when we are silently probing the primary model.
-
#remove_model(index) ⇒ Object
Remove a model by index Returns true if removed, false if index out of range or it’s the last model.
- #save(config_file = CONFIG_FILE) ⇒ Object
-
#set_model_type(index, type) ⇒ Object
Set a model’s type (default or lite) Ensures only one model has each type Returns true if successful.
-
#switch_model(index) ⇒ Object
Switch to model by index Updates the type: default to the selected model Returns true if switched, false if index out of range.
-
#to_yaml ⇒ Object
Convert to YAML format (top-level array) Auto-injected lite models (auto_injected: true) are excluded from persistence — they are regenerated at load time from the provider preset.
Constructor Details
#initialize(options = {}) ⇒ AgentConfig
Returns a new instance of AgentConfig.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/clacky/agent_config.rb', line 158 def initialize( = {}) @permission_mode = ([:permission_mode]) @max_tokens = [:max_tokens] || 8192 @verbose = [:verbose] || false @enable_compression = [:enable_compression].nil? ? true : [:enable_compression] # Enable prompt caching by default for cost savings @enable_prompt_caching = [:enable_prompt_caching].nil? ? true : [:enable_prompt_caching] # Models configuration @models = [:models] || [] @current_model_index = [:current_model_index] || 0 # Memory and skill evolution configuration @memory_update_enabled = [:memory_update_enabled].nil? ? true : [:memory_update_enabled] @skill_evolution = [:skill_evolution] || { enabled: true, auto_create_threshold: 12, reflection_mode: "llm_analysis" } end |
Instance Attribute Details
#current_model_index ⇒ Object
Returns the value of attribute current_model_index.
153 154 155 |
# File 'lib/clacky/agent_config.rb', line 153 def current_model_index @current_model_index end |
#enable_compression ⇒ Object
Returns the value of attribute enable_compression.
153 154 155 |
# File 'lib/clacky/agent_config.rb', line 153 def enable_compression @enable_compression end |
#enable_prompt_caching ⇒ Object
Returns the value of attribute enable_prompt_caching.
153 154 155 |
# File 'lib/clacky/agent_config.rb', line 153 def enable_prompt_caching @enable_prompt_caching end |
#max_tokens ⇒ Object
Returns the value of attribute max_tokens.
153 154 155 |
# File 'lib/clacky/agent_config.rb', line 153 def max_tokens @max_tokens end |
#memory_update_enabled ⇒ Object
Returns the value of attribute memory_update_enabled.
153 154 155 |
# File 'lib/clacky/agent_config.rb', line 153 def memory_update_enabled @memory_update_enabled end |
#models ⇒ Object
Returns the value of attribute models.
153 154 155 |
# File 'lib/clacky/agent_config.rb', line 153 def models @models end |
#permission_mode ⇒ Object
Returns the value of attribute permission_mode.
153 154 155 |
# File 'lib/clacky/agent_config.rb', line 153 def @permission_mode end |
#skill_evolution ⇒ Object
Returns the value of attribute skill_evolution.
153 154 155 |
# File 'lib/clacky/agent_config.rb', line 153 def skill_evolution @skill_evolution end |
#verbose ⇒ Object
Returns the value of attribute verbose.
153 154 155 |
# File 'lib/clacky/agent_config.rb', line 153 def verbose @verbose end |
Class Method Details
.load(config_file = CONFIG_FILE) ⇒ Object
Load configuration from file
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/clacky/agent_config.rb', line 180 def self.load(config_file = CONFIG_FILE) # Load from config file first if File.exist?(config_file) data = YAML.load_file(config_file) else data = nil end # Parse models from config models = parse_models(data) # Priority: config file > CLACKY_XXX env vars > ClaudeCode env vars if models.empty? # Try CLACKY_XXX environment variables first if ClackyEnv.default_configured? models << ClackyEnv.default_model_config # ClaudeCode (Anthropic) environment variable support is disabled # elsif ClaudeCodeEnv.configured? # models << { # "type" => "default", # "api_key" => ClaudeCodeEnv.api_key, # "base_url" => ClaudeCodeEnv.base_url, # "model" => CLAUDE_DEFAULT_MODEL, # "anthropic_format" => true # } end # Add CLACKY_LITE_XXX if configured (only when loading from env) if ClackyEnv.lite_configured? models << ClackyEnv.lite_model_config end else # Config file exists, but check if we need to add env-based models # Only add if no model with that type exists has_default = models.any? { |m| m["type"] == "default" } has_lite = models.any? { |m| m["type"] == "lite" } # Add CLACKY default if not in config and env is set if !has_default && ClackyEnv.default_configured? models << ClackyEnv.default_model_config end # Add CLACKY lite if not in config and env is set if !has_lite && ClackyEnv.lite_configured? models << ClackyEnv.lite_model_config end # Ensure at least one model has type: default # If no model has type: default, assign it to the first model unless models.any? { |m| m["type"] == "default" } models.first["type"] = "default" if models.any? end end # Auto-inject lite model from provider preset when: # 1. A default model exists # 2. No lite model is configured yet (neither in file nor env) # 3. The default model's provider has a known lite_model # The injected lite model is runtime-only (not persisted to config.yml) inject_provider_lite_model(models) # Find the index of the model marked as "default" (type: default) # Fall back to 0 if no model has type: default default_index = models.find_index { |m| m["type"] == "default" } || 0 new(models: models, current_model_index: default_index) end |
Instance Method Details
#activate_fallback!(fallback_model_name) ⇒ Object
Switch to fallback model and start the cooling-off clock. Idempotent — calling again while already in :fallback_active renews the timestamp.
435 436 437 438 439 |
# File 'lib/clacky/agent_config.rb', line 435 def activate_fallback!(fallback_model_name) @fallback_state = :fallback_active @fallback_since = Time.now @fallback_model = fallback_model_name end |
#add_model(model:, api_key:, base_url:, anthropic_format: false, type: nil) ⇒ Object
Add a new model configuration
386 387 388 389 390 391 392 393 394 |
# File 'lib/clacky/agent_config.rb', line 386 def add_model(model:, api_key:, base_url:, anthropic_format: false, type: nil) @models << { "api_key" => api_key, "base_url" => base_url, "model" => model, "anthropic_format" => anthropic_format, "type" => type }.compact end |
#anthropic_format? ⇒ Boolean
Check if should use Anthropic format for current model
376 377 378 |
# File 'lib/clacky/agent_config.rb', line 376 def anthropic_format? current_model&.dig("anthropic_format") || false end |
#api_key ⇒ Object
Get API key for current model
343 344 345 |
# File 'lib/clacky/agent_config.rb', line 343 def api_key current_model&.dig("api_key") end |
#api_key=(value) ⇒ Object
Set API key for current model
348 349 350 351 |
# File 'lib/clacky/agent_config.rb', line 348 def api_key=(value) return unless current_model current_model["api_key"] = value end |
#base_url ⇒ Object
Get base URL for current model
354 355 356 |
# File 'lib/clacky/agent_config.rb', line 354 def base_url current_model&.dig("base_url") end |
#base_url=(value) ⇒ Object
Set base URL for current model
359 360 361 362 |
# File 'lib/clacky/agent_config.rb', line 359 def base_url=(value) return unless current_model current_model["base_url"] = value end |
#bedrock? ⇒ Boolean
Check if current model uses Bedrock Converse API (ABSK key prefix or abs- model prefix)
381 382 383 |
# File 'lib/clacky/agent_config.rb', line 381 def bedrock? Clacky::MessageFormat::Bedrock.bedrock_api_key?(api_key.to_s, model_name.to_s) end |
#confirm_fallback_ok! ⇒ Object
Called when a successful API response is received. If we were :probing (testing primary after cooling-off), this confirms the primary model is healthy again and resets everything. No-op in :primary_ok or :fallback_active states.
456 457 458 459 460 461 462 |
# File 'lib/clacky/agent_config.rb', line 456 def confirm_fallback_ok! return unless @fallback_state == :probing @fallback_state = nil @fallback_since = nil @fallback_model = nil end |
#current_model ⇒ Object
Get current model configuration Looks for type: default first, falls back to current_model_index
309 310 311 312 |
# File 'lib/clacky/agent_config.rb', line 309 def current_model return nil if @models.empty? @models[@current_model_index] end |
#deep_copy ⇒ Object
Save configuration to file Deep copy — models array contains mutable Hashes, so a shallow dup would let the copy share the same Hash objects with the original, causing Settings changes to silently mutate already-running session configs. JSON round-trip is the cleanest approach since @models is pure JSON-able data.
282 283 284 285 286 |
# File 'lib/clacky/agent_config.rb', line 282 def deep_copy copy = dup copy.instance_variable_set(:@models, JSON.parse(JSON.generate(@models))) copy end |
#default_model ⇒ Object
Get the default model (type: default) Falls back to current_model for backward compatibility
404 405 406 |
# File 'lib/clacky/agent_config.rb', line 404 def default_model find_model_by_type("default") || current_model end |
#effective_model_name ⇒ Object
The effective model name to use for API calls.
-
:primary_ok / nil → configured model_name (primary)
-
:fallback_active → fallback model
-
:probing → configured model_name (trying primary silently)
479 480 481 482 483 484 485 486 487 |
# File 'lib/clacky/agent_config.rb', line 479 def effective_model_name case @fallback_state when :fallback_active @fallback_model || model_name else # :primary_ok (nil) and :probing both use the primary model model_name end end |
#fallback_active? ⇒ Boolean
Returns true when a fallback model is currently being used (:fallback_active or :probing states).
466 467 468 |
# File 'lib/clacky/agent_config.rb', line 466 def fallback_active? @fallback_state == :fallback_active || @fallback_state == :probing end |
#fallback_model_for(model_name) ⇒ String?
Look up the fallback model name for the given model name. Uses the provider preset’s fallback_models table. Returns nil if no fallback is configured for this model.
422 423 424 425 426 427 428 429 430 |
# File 'lib/clacky/agent_config.rb', line 422 def fallback_model_for(model_name) m = current_model return nil unless m provider_id = Clacky::Providers.find_by_base_url(m["base_url"]) return nil unless provider_id Clacky::Providers.fallback_model(provider_id, model_name) end |
#find_model_by_type(type) ⇒ Object
Find model by type (default or lite) Returns the model hash or nil if not found
398 399 400 |
# File 'lib/clacky/agent_config.rb', line 398 def find_model_by_type(type) @models.find { |m| m["type"] == type } end |
#get_model(index) ⇒ Object
Get model by index
315 316 317 |
# File 'lib/clacky/agent_config.rb', line 315 def get_model(index) @models[index] end |
#lite_model ⇒ Object
Get the lite model (type: lite) Returns nil if no lite model configured
410 411 412 |
# File 'lib/clacky/agent_config.rb', line 410 def lite_model find_model_by_type("lite") end |
#maybe_start_probing ⇒ Object
Called at the start of every call_llm. If cooling-off has expired, transition from :fallback_active → :probing so the next request will silently test the primary model. No-op in any other state.
445 446 447 448 449 450 |
# File 'lib/clacky/agent_config.rb', line 445 def maybe_start_probing return unless @fallback_state == :fallback_active return unless @fallback_since && (Time.now - @fallback_since) >= FALLBACK_COOLING_OFF_SECONDS @fallback_state = :probing end |
#model_name ⇒ Object
Get model name for current model
365 366 367 |
# File 'lib/clacky/agent_config.rb', line 365 def model_name current_model&.dig("model") end |
#model_name=(value) ⇒ Object
Set model name for current model
370 371 372 373 |
# File 'lib/clacky/agent_config.rb', line 370 def model_name=(value) return unless current_model current_model["model"] = value end |
#model_names ⇒ Object
List all model names
338 339 340 |
# File 'lib/clacky/agent_config.rb', line 338 def model_names @models.map { |m| m["model"] } end |
#models_configured? ⇒ Boolean
Check if any model is configured
304 305 306 |
# File 'lib/clacky/agent_config.rb', line 304 def models_configured? !@models.empty? && !current_model.nil? end |
#probing? ⇒ Boolean
Returns true only when we are silently probing the primary model.
471 472 473 |
# File 'lib/clacky/agent_config.rb', line 471 def probing? @fallback_state == :probing end |
#remove_model(index) ⇒ Object
Remove a model by index Returns true if removed, false if index out of range or it’s the last model
527 528 529 530 531 532 533 534 535 536 537 538 539 540 |
# File 'lib/clacky/agent_config.rb', line 527 def remove_model(index) # Don't allow removing the last model return false if @models.length <= 1 return false if index < 0 || index >= @models.length @models.delete_at(index) # Adjust current_model_index if necessary if @current_model_index >= @models.length @current_model_index = @models.length - 1 end true end |
#save(config_file = CONFIG_FILE) ⇒ Object
288 289 290 291 292 293 |
# File 'lib/clacky/agent_config.rb', line 288 def save(config_file = CONFIG_FILE) config_dir = File.dirname(config_file) FileUtils.mkdir_p(config_dir) File.write(config_file, to_yaml) FileUtils.chmod(0o600, config_file) end |
#set_model_type(index, type) ⇒ Object
Set a model’s type (default or lite) Ensures only one model has each type Returns true if successful
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 |
# File 'lib/clacky/agent_config.rb', line 505 def set_model_type(index, type) return false if index < 0 || index >= @models.length return false unless ["default", "lite", nil].include?(type) if type # Remove type from any other model that has it @models.each do |m| m.delete("type") if m["type"] == type end # Set type on target model @models[index]["type"] = type else # Remove type from target model @models[index].delete("type") end true end |
#switch_model(index) ⇒ Object
Switch to model by index Updates the type: default to the selected model Returns true if switched, false if index out of range
322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/clacky/agent_config.rb', line 322 def switch_model(index) return false if index < 0 || index >= @models.length # Remove type: default from all models @models.each { |m| m.delete("type") if m["type"] == "default" } # Set type: default on the selected model @models[index]["type"] = "default" # Update current_model_index for backward compatibility @current_model_index = index true end |
#to_yaml ⇒ Object
Convert to YAML format (top-level array) Auto-injected lite models (auto_injected: true) are excluded from persistence —they are regenerated at load time from the provider preset.
298 299 300 301 |
# File 'lib/clacky/agent_config.rb', line 298 def to_yaml persistable = @models.reject { |m| m["auto_injected"] } YAML.dump(persistable) end |