Class: RailsConsoleAi::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_console_ai/configuration.rb

Constant Summary collapse

PROVIDERS =
%i[anthropic openai local bedrock].freeze
MODEL_FAMILIES =

Per-family model attributes, matched by substring so one entry covers every ID variant of a family: bare Anthropic IDs (claude-sonnet-5), dated snapshots (claude-haiku-4-5-20251001), and Bedrock inference profiles (us.anthropic.claude-sonnet-5, global.anthropic.claude-opus-4-6-v1).

input/output are $ per MTok (converted to per-token in .pricing_for). Cache pricing is derived: read = 0.1x input, write = 1.25x input. temperature: false marks families that reject the temperature parameter (removed on opus-4-7+, sonnet-5, and fable-5).

{
  'claude-fable-5'    => { input: 10.0, output: 50.0, max_tokens: 16_000, temperature: false },
  'claude-opus-4-8'   => { input: 5.0,  output: 25.0, max_tokens: 16_000, temperature: false },
  'claude-opus-4-7'   => { input: 5.0,  output: 25.0, max_tokens: 16_000, temperature: false },
  'claude-opus-4-6'   => { input: 5.0,  output: 25.0, max_tokens: 16_000, temperature: true },
  'claude-sonnet-5'   => { input: 3.0,  output: 15.0, max_tokens: 16_000, temperature: false },
  'claude-sonnet-4-6' => { input: 3.0,  output: 15.0, max_tokens: 16_000, temperature: true },
  'claude-haiku-4-5'  => { input: 1.0,  output: 5.0,  max_tokens: 16_000, temperature: true },
}.freeze
MODEL_FAMILY_KEYS =

Family keys sorted longest-first so a more specific family always wins if keys ever overlap (e.g. a future 'claude-sonnet-5-5' entry would match before 'claude-sonnet-5').

MODEL_FAMILIES.keys.sort_by { |k| -k.length }.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/rails_console_ai/configuration.rb', line 67

def initialize
  @provider     = :anthropic
  @api_key      = nil
  @model        = nil
  @thinking_model = nil
  @max_tokens   = nil
  @auto_execute = false
  @temperature  = 0.2
  @timeout      = 30
  @debug        = false
  @max_tool_rounds = 200
  @storage_adapter  = nil
  @memories_enabled = true
  @session_logging  = true
  @connection_class = nil
  @admin_username   = nil
  @admin_password   = nil
  @authenticate     = nil
  @safety_guards    = nil
  @slack_bot_token  = nil
  @slack_app_token  = nil
  @slack_channel_ids = nil
  @slack_allowed_usernames = nil
  @local_url        = 'http://localhost:11434'
  @local_model      = 'qwen2.5:7b'
  @local_api_key    = nil
  @bedrock_region   = nil
  @code_search_paths = %w[app]
  @channels = {}
  @bypass_guards_for_methods = []
  @user_extra_info = {}
  @sub_agent_max_rounds = 15
  @sub_agent_model = nil
end

Instance Attribute Details

#admin_passwordObject

Returns the value of attribute admin_password.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def admin_password
  @admin_password
end

#admin_usernameObject

Returns the value of attribute admin_username.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def admin_username
  @admin_username
end

#api_keyObject

Returns the value of attribute api_key.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def api_key
  @api_key
end

#authenticateObject

Returns the value of attribute authenticate.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def authenticate
  @authenticate
end

#auto_executeObject

Returns the value of attribute auto_execute.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def auto_execute
  @auto_execute
end

#bedrock_regionObject

Returns the value of attribute bedrock_region.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def bedrock_region
  @bedrock_region
end

#bypass_guards_for_methodsObject

Returns the value of attribute bypass_guards_for_methods.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def bypass_guards_for_methods
  @bypass_guards_for_methods
end

#channelsObject

Returns the value of attribute channels.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def channels
  @channels
end

#code_search_pathsObject

Returns the value of attribute code_search_paths.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def code_search_paths
  @code_search_paths
end

#connection_classObject

Returns the value of attribute connection_class.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def connection_class
  @connection_class
end

#debugObject

Returns the value of attribute debug.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def debug
  @debug
end

#local_api_keyObject

Returns the value of attribute local_api_key.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def local_api_key
  @local_api_key
end

#local_modelObject

Returns the value of attribute local_model.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def local_model
  @local_model
end

#local_urlObject

Returns the value of attribute local_url.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def local_url
  @local_url
end

#max_tokensObject

Returns the value of attribute max_tokens.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def max_tokens
  @max_tokens
end

#max_tool_roundsObject

Returns the value of attribute max_tool_rounds.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def max_tool_rounds
  @max_tool_rounds
end

#memories_enabledObject

Returns the value of attribute memories_enabled.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def memories_enabled
  @memories_enabled
end

#modelObject

Returns the value of attribute model.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def model
  @model
end

#providerObject

Returns the value of attribute provider.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def provider
  @provider
end

#session_loggingObject

Returns the value of attribute session_logging.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def session_logging
  @session_logging
end

#slack_allowed_usernamesObject

Returns the value of attribute slack_allowed_usernames.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def slack_allowed_usernames
  @slack_allowed_usernames
end

#slack_app_tokenObject

Returns the value of attribute slack_app_token.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def slack_app_token
  @slack_app_token
end

#slack_bot_tokenObject

Returns the value of attribute slack_bot_token.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def slack_bot_token
  @slack_bot_token
end

#slack_channel_idsObject

Returns the value of attribute slack_channel_ids.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def slack_channel_ids
  @slack_channel_ids
end

#storage_adapterObject

Returns the value of attribute storage_adapter.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def storage_adapter
  @storage_adapter
end

#sub_agent_max_roundsObject

Returns the value of attribute sub_agent_max_rounds.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def sub_agent_max_rounds
  @sub_agent_max_rounds
end

#sub_agent_modelObject

Returns the value of attribute sub_agent_model.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def sub_agent_model
  @sub_agent_model
end

#temperatureObject

Returns the value of attribute temperature.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def temperature
  @temperature
end

#thinking_modelObject

Returns the value of attribute thinking_model.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def thinking_model
  @thinking_model
end

#timeoutObject

Returns the value of attribute timeout.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def timeout
  @timeout
end

#user_extra_infoObject

Returns the value of attribute user_extra_info.



50
51
52
# File 'lib/rails_console_ai/configuration.rb', line 50

def user_extra_info
  @user_extra_info
end

Class Method Details

.model_family(model_id) ⇒ Object

Returns the family attributes for a model ID, or nil for unknown models.



30
31
32
33
34
# File 'lib/rails_console_ai/configuration.rb', line 30

def self.model_family(model_id)
  return nil unless model_id
  key = MODEL_FAMILY_KEYS.find { |k| model_id.include?(k) }
  key && MODEL_FAMILIES[key]
end

.pricing_for(model_id) ⇒ Object

Per-token pricing for a model ID, matched by family. Returns { input:, output:, cache_read:, cache_write: } or nil for unknown models.



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/rails_console_ai/configuration.rb', line 38

def self.pricing_for(model_id)
  family = model_family(model_id)
  return nil unless family
  input = family[:input] / 1_000_000
  {
    input: input,
    output: family[:output] / 1_000_000,
    cache_read: input * 0.1,
    cache_write: input * 1.25,
  }
end

Instance Method Details

#channel_setting(mode, key) ⇒ Object

Look up a per-channel setting with backward compatibility. Falls back to top-level slack_* config when channels hash doesn't have the key.



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/rails_console_ai/configuration.rb', line 109

def channel_setting(mode, key)
  channel_cfg = @channels[mode.to_s] || {}
  value = channel_cfg[key.to_s]

  # Backward compatibility: slack_allowed_usernames → channels.slack.allowed_usernames
  if value.nil? && mode.to_s == 'slack' && key.to_s == 'allowed_usernames'
    value = @slack_allowed_usernames
  end

  value
end

#resolve_user_extra_info(username) ⇒ Object



102
103
104
105
# File 'lib/rails_console_ai/configuration.rb', line 102

def resolve_user_extra_info(username)
  return nil if @user_extra_info.nil? || @user_extra_info.empty? || username.nil?
  @user_extra_info[username.to_s.downcase]
end

#resolved_api_keyObject



175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/rails_console_ai/configuration.rb', line 175

def resolved_api_key
  return @api_key if @api_key && !@api_key.empty?

  case @provider
  when :anthropic
    ENV['ANTHROPIC_API_KEY']
  when :openai
    ENV['OPENAI_API_KEY']
  when :local
    @local_api_key || 'no-key'
  when :bedrock
    'aws-sdk'
  end
end

#resolved_max_tokensObject



205
206
207
208
209
210
# File 'lib/rails_console_ai/configuration.rb', line 205

def resolved_max_tokens
  return @max_tokens if @max_tokens

  family = self.class.model_family(resolved_model)
  family ? family[:max_tokens] : 4096
end

#resolved_modelObject



190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/rails_console_ai/configuration.rb', line 190

def resolved_model
  return @model if @model && !@model.empty?

  case @provider
  when :anthropic
    'claude-sonnet-5'
  when :openai
    'gpt-5.3-codex'
  when :local
    @local_model
  when :bedrock
    'us.anthropic.claude-sonnet-5'
  end
end

#resolved_temperatureObject

Returns nil for model families that reject the temperature parameter (opus-4-7+, sonnet-5, fable-5) so providers omit the field from the request.



214
215
216
217
218
# File 'lib/rails_console_ai/configuration.rb', line 214

def resolved_temperature
  family = self.class.model_family(resolved_model)
  return nil if family && family[:temperature] == false
  @temperature
end

#resolved_thinking_modelObject



220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/rails_console_ai/configuration.rb', line 220

def resolved_thinking_model
  return @thinking_model if @thinking_model && !@thinking_model.empty?

  case @provider
  when :anthropic
    'claude-opus-4-8'
  when :openai
    'gpt-5.3-codex'
  when :local
    @local_model
  when :bedrock
    'us.anthropic.claude-opus-4-8'
  end
end

#resolved_timeoutObject



235
236
237
# File 'lib/rails_console_ai/configuration.rb', line 235

def resolved_timeout
  @provider == :local ? [@timeout, 300].max : @timeout
end

#safety_guard(name, &block) ⇒ Object

Register a custom safety guard by name with an around-block.

config.safety_guard :mailers do |&execute|
ActionMailer::Base.perform_deliveries = false
execute.call
ensure
ActionMailer::Base.perform_deliveries = true
end


145
146
147
# File 'lib/rails_console_ai/configuration.rb', line 145

def safety_guard(name, &block)
  safety_guards.add(name, &block)
end

#safety_guardsObject



130
131
132
133
134
135
# File 'lib/rails_console_ai/configuration.rb', line 130

def safety_guards
  @safety_guards ||= begin
    require 'rails_console_ai/safety_guards'
    SafetyGuards.new
  end
end

#use_builtin_safety_guard(name, allow: nil) ⇒ Object

Register a built-in safety guard by name. Available: :database_writes, :http_mutations, :mailers

Options:

allow: Array of strings or regexps to allowlist for this guard.
- :http_mutations  → hosts (e.g. "s3.amazonaws.com", /googleapis\.com/)
- :database_writes → table names (e.g. "rails_console_ai_sessions")


156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/rails_console_ai/configuration.rb', line 156

def use_builtin_safety_guard(name, allow: nil)
  require 'rails_console_ai/safety_guards'
  guard_name = name.to_sym
  case guard_name
  when :database_writes
    safety_guards.add(:database_writes, &BuiltinGuards.database_writes)
  when :http_mutations
    safety_guards.add(:http_mutations, &BuiltinGuards.http_mutations)
  when :mailers
    safety_guards.add(:mailers, &BuiltinGuards.mailers)
  else
    raise ConfigurationError, "Unknown built-in safety guard: #{name}. Available: database_writes, http_mutations, mailers"
  end

  if allow
    Array(allow).each { |key| safety_guards.allow_global(guard_name, key) }
  end
end

#username_allowed?(mode, key, username) ⇒ Boolean

Check if a username is permitted by a channel setting. Returns true when the setting is nil (not configured = no restriction).

Returns:

  • (Boolean)


123
124
125
126
127
128
# File 'lib/rails_console_ai/configuration.rb', line 123

def username_allowed?(mode, key, username)
  list = channel_setting(mode, key)
  return true if list.nil?
  normalized = Array(list).map(&:to_s).map(&:downcase)
  normalized.include?('all') || normalized.include?(username.to_s.downcase)
end

#validate!Object



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/rails_console_ai/configuration.rb', line 239

def validate!
  unless PROVIDERS.include?(@provider)
    raise ConfigurationError, "Unknown provider: #{@provider}. Valid: #{PROVIDERS.join(', ')}"
  end

  if @provider == :local
    raise ConfigurationError, "No local_url configured for :local provider." unless @local_url && !@local_url.empty?
  elsif @provider == :bedrock
    begin
      require 'aws-sdk-bedrockruntime'
    rescue LoadError
      raise ConfigurationError,
        "aws-sdk-bedrockruntime gem is required for the :bedrock provider. Add it to your Gemfile."
    end
  else
    unless resolved_api_key
      env_var = @provider == :anthropic ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'
      raise ConfigurationError, "No API key. Set config.api_key or #{env_var} env var."
    end
  end
end