Class: RubynCode::Config::Settings

Inherits:
Object
  • Object
show all
Defined in:
lib/rubyn_code/config/settings.rb

Defined Under Namespace

Classes: LoadError

Constant Summary collapse

CONFIGURABLE_KEYS =
%i[
  provider model model_mode max_iterations max_sub_agent_iterations max_output_chars
  context_threshold_tokens micro_compact_keep_recent
  poll_interval idle_timeout
  session_budget_usd daily_budget_usd
  oauth_client_id oauth_redirect_uri oauth_authorize_url
  oauth_token_url oauth_scopes
  skills_autoload
  chisel_mode
].freeze
DEFAULT_MAP =
{
  provider: Defaults::DEFAULT_PROVIDER,
  model: Defaults::DEFAULT_MODEL,
  model_mode: Defaults::MODEL_MODE,
  max_iterations: Defaults::MAX_ITERATIONS,
  max_sub_agent_iterations: Defaults::MAX_SUB_AGENT_ITERATIONS,
  max_output_chars: Defaults::MAX_OUTPUT_CHARS,
  context_threshold_tokens: Defaults::CONTEXT_THRESHOLD_TOKENS,
  micro_compact_keep_recent: Defaults::MICRO_COMPACT_KEEP_RECENT,
  poll_interval: Defaults::POLL_INTERVAL,
  idle_timeout: Defaults::IDLE_TIMEOUT,
  session_budget_usd: Defaults::SESSION_BUDGET_USD,
  daily_budget_usd: Defaults::DAILY_BUDGET_USD,
  oauth_client_id: Defaults::OAUTH_CLIENT_ID,
  oauth_redirect_uri: Defaults::OAUTH_REDIRECT_URI,
  oauth_authorize_url: Defaults::OAUTH_AUTHORIZE_URL,
  oauth_token_url: Defaults::OAUTH_TOKEN_URL,
  oauth_scopes: Defaults::OAUTH_SCOPES,
  skills_autoload: Defaults::SKILLS_AUTOLOAD,
  chisel_mode: Defaults::CHISEL_MODE
}.freeze
DEFAULT_PROVIDER_MODELS =

Default model tiers per built-in provider. Used by seed_config! and backfill_provider_models! so new and existing configs stay in sync.

{
  'anthropic' => {
    'env_key' => 'ANTHROPIC_API_KEY',
    'models' => { 'cheap' => 'claude-haiku-4-5', 'mid' => 'claude-sonnet-4-6', 'top' => 'claude-opus-4-8' }
  },
  'openai' => {
    'env_key' => 'OPENAI_API_KEY',
    'models' => { 'cheap' => 'gpt-5.4-nano', 'mid' => 'gpt-5.4-mini', 'top' => 'gpt-5.4' }
  }
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_path: Defaults::CONFIG_FILE) ⇒ Settings

Returns a new instance of Settings.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/rubyn_code/config/settings.rb', line 48

def initialize(config_path: Defaults::CONFIG_FILE)
  # When tests run, isolate Settings from the developer's personal
  # ~/.rubyn-code/config.yml so a stray `provider: minimax` can't
  # shadow the test expectations.  The test config lives in
  # tmpdir, is process-pid-scoped, and is harmless if it leaks.
  config_path = self.class.test_config_path if config_path == Defaults::CONFIG_FILE && ENV['RUBYN_TESTING']

  @config_path = config_path
  @data = {}
  ensure_home_directory!
  seed_config! unless File.exist?(@config_path)
  load!
  backfill_provider_models!
end

Instance Attribute Details

#config_pathObject (readonly)

Returns the value of attribute config_path.



46
47
48
# File 'lib/rubyn_code/config/settings.rb', line 46

def config_path
  @config_path
end

#dataObject (readonly)

Returns the value of attribute data.



46
47
48
# File 'lib/rubyn_code/config/settings.rb', line 46

def data
  @data
end

Class Method Details

.test_config_pathString

Returns a per-pid path under tmpdir used when RUBYN_TESTING is set.

Returns:

  • (String)

    a per-pid path under tmpdir used when RUBYN_TESTING is set



65
66
67
# File 'lib/rubyn_code/config/settings.rb', line 65

def self.test_config_path
  @test_config_path ||= File.join(Dir.tmpdir, "rubyn-test-config-#{Process.pid}.yml")
end

Instance Method Details

#add_provider(name, base_url:, env_key: nil, models: [], pricing: {}, api_format: nil) ⇒ Object

Add or update a provider in the config and persist to disk.

Parameters:

  • name (String)

    provider name (e.g., ‘groq’)

  • base_url (String)

    API base URL

  • env_key (String, nil) (defaults to: nil)

    environment variable for the API key

  • models (Array<String>) (defaults to: [])

    available model names

  • pricing (Hash) (defaults to: {})

    model => [input_rate, output_rate]

  • api_format (String, nil) (defaults to: nil)

    API format (‘openai’ or ‘anthropic’)



136
137
138
139
140
141
142
# File 'lib/rubyn_code/config/settings.rb', line 136

def add_provider(name, base_url:, env_key: nil, models: [], pricing: {}, api_format: nil) # rubocop:disable Metrics/ParameterLists -- all optional kwargs with defaults
  @data['providers'] ||= {}
  @data['providers'][name.to_s] = build_provider_hash(
    base_url: base_url, env_key: env_key, models: models, pricing: pricing, api_format: api_format
  )
  save!
end

#custom_pricingObject

Returns all user-configured pricing as { model => [input, output] }



145
146
147
148
149
150
151
152
# File 'lib/rubyn_code/config/settings.rb', line 145

def custom_pricing
  providers = @data['providers']
  return {} unless providers.is_a?(Hash)

  providers.each_with_object({}) do |(_, cfg), acc|
    merge_provider_pricing(cfg, acc)
  end
end

#dangerous_patternsObject



113
# File 'lib/rubyn_code/config/settings.rb', line 113

def dangerous_patterns = Defaults::DANGEROUS_PATTERNS

#db_fileObject



108
# File 'lib/rubyn_code/config/settings.rb', line 108

def db_file = Defaults::DB_FILE

#get(key, default = nil) ⇒ Object



80
81
82
83
# File 'lib/rubyn_code/config/settings.rb', line 80

def get(key, default = nil)
  sym = key.to_sym
  @data.fetch(key.to_s) { DEFAULT_MAP.fetch(sym, default) }
end

#home_dirObject



107
# File 'lib/rubyn_code/config/settings.rb', line 107

def home_dir = Defaults::HOME_DIR

#memories_dirObject



111
# File 'lib/rubyn_code/config/settings.rb', line 111

def memories_dir = Defaults::MEMORIES_DIR

#provider_config(name) ⇒ Object

Returns config hash for a custom provider, or nil if not configured. Reads from ‘providers.<name>` in config.yml.

Expected keys: base_url, env_key, models, pricing pricing is a hash of model_name => [input_rate, output_rate]



121
122
123
124
125
126
# File 'lib/rubyn_code/config/settings.rb', line 121

def provider_config(name)
  providers = @data.dig('providers', name.to_s)
  return nil unless providers.is_a?(Hash)

  providers.transform_keys(&:to_s)
end

#reload!Object



99
100
101
# File 'lib/rubyn_code/config/settings.rb', line 99

def reload!
  load!
end

#save!Object



89
90
91
92
93
94
95
96
97
# File 'lib/rubyn_code/config/settings.rb', line 89

def save!
  ensure_home_directory!
  File.write(@config_path, YAML.dump(@data))
  File.chmod(0o600, @config_path)
rescue Errno::EACCES => e
  raise LoadError, "Permission denied writing config to #{@config_path}: #{e.message}"
rescue SystemCallError => e
  raise LoadError, "Failed to save config to #{@config_path}: #{e.message}"
end

#scrub_env_varsObject



114
# File 'lib/rubyn_code/config/settings.rb', line 114

def scrub_env_vars = Defaults::SCRUB_ENV_VARS

#sessions_dirObject



110
# File 'lib/rubyn_code/config/settings.rb', line 110

def sessions_dir = Defaults::SESSIONS_DIR

#set(key, value) ⇒ Object



85
86
87
# File 'lib/rubyn_code/config/settings.rb', line 85

def set(key, value)
  @data[key.to_s] = value
end

#to_hObject



103
104
105
# File 'lib/rubyn_code/config/settings.rb', line 103

def to_h
  DEFAULT_MAP.transform_keys(&:to_s).merge(@data)
end

#tokens_fileObject



109
# File 'lib/rubyn_code/config/settings.rb', line 109

def tokens_file = Defaults::TOKENS_FILE