Module: Legion::LLM::Call::ClaudeConfigLoader

Extended by:
Legion::Logging::Helper
Defined in:
lib/legion/llm/call/claude_config_loader.rb

Constant Summary collapse

SECRET_URI_PATTERN =
%r{\A(?:env|vault|lease)://}

Class Method Summary collapse

Class Method Details

.anthropic_api_keyObject



44
45
46
47
48
49
50
# File 'lib/legion/llm/call/claude_config_loader.rb', line 44

def anthropic_api_key
  config = merged_config
  first_present(
    config[:anthropicApiKey],
    config.dig(:env, :ANTHROPIC_API_KEY)
  )
end

.apply_api_keys(config) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/legion/llm/call/claude_config_loader.rb', line 90

def apply_api_keys(config)
  llm = Legion::LLM.settings
  providers = llm[:providers]

  anthropic_key = first_present(config[:anthropicApiKey], config.dig(:env, :ANTHROPIC_API_KEY))
  if anthropic_key && !setting_has_usable_credential?(providers.dig(:anthropic, :api_key))
    providers[:anthropic][:api_key] = anthropic_key
    log.debug 'Imported Anthropic API key from Claude CLI config'
  end

  openai_key = first_present(config[:openaiApiKey], config.dig(:env, :OPENAI_API_KEY), config.dig(:env, :CODEX_API_KEY))
  if openai_key && !setting_has_usable_credential?(providers.dig(:openai, :api_key))
    providers[:openai][:api_key] = openai_key
    log.debug 'Imported OpenAI API key from Claude CLI config'
  end

  bedrock_token = bedrock_bearer_token
  return unless bedrock_token && !setting_has_usable_credential?(providers.dig(:bedrock, :bearer_token))

  providers[:bedrock][:bearer_token] = bedrock_token
  log.debug 'Imported Bedrock bearer token from Claude settings.json env section'
end

.apply_claude_config(config) ⇒ Object



85
86
87
88
# File 'lib/legion/llm/call/claude_config_loader.rb', line 85

def apply_claude_config(config)
  apply_api_keys(config)
  apply_model_preference(config)
end

.apply_model_preference(config) ⇒ Object



113
114
115
116
117
118
119
120
121
122
# File 'lib/legion/llm/call/claude_config_loader.rb', line 113

def apply_model_preference(config)
  return unless config[:preferredModel] || config[:model]

  model = config[:preferredModel] || config[:model]
  llm = Legion::LLM.settings
  return if llm[:default_model]

  llm[:default_model] = model
  log.debug "Imported model preference from Claude CLI config: #{model}"
end

.bedrock_bearer_tokenObject



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/legion/llm/call/claude_config_loader.rb', line 61

def bedrock_bearer_token
  env = read_json(claude_settings_path)[:env]
  return nil unless env.is_a?(Hash)

  direct = first_present(env[:AWS_BEARER_TOKEN_BEDROCK], env['AWS_BEARER_TOKEN_BEDROCK'])
  return direct if direct

  match = env.find do |key, value|
    name = key.to_s.upcase
    next false unless name.include?('AWS')
    next false unless name.include?('BEARER')
    next false unless name.include?('TOKEN')
    next false unless name.include?('BEDROCK')

    !normalize_secret(value).nil?
  end
  normalize_secret(match&.last)
end

.claude_config_pathObject



20
21
22
# File 'lib/legion/llm/call/claude_config_loader.rb', line 20

def claude_config_path
  File.expand_path(Legion::LLM.settings.dig(:claude_cli, :config_path) || '~/.claude.json')
end

.claude_settings_pathObject



16
17
18
# File 'lib/legion/llm/call/claude_config_loader.rb', line 16

def claude_settings_path
  File.expand_path(Legion::LLM.settings.dig(:claude_cli, :settings_path) || '~/.claude/settings.json')
end

.first_present(*values) ⇒ Object



152
153
154
155
156
157
158
# File 'lib/legion/llm/call/claude_config_loader.rb', line 152

def first_present(*values)
  values.each do |value|
    normalized = normalize_secret(value)
    return normalized unless normalized.nil?
  end
  nil
end

.loadObject



24
25
26
27
28
29
# File 'lib/legion/llm/call/claude_config_loader.rb', line 24

def load
  config = merged_config
  return if config.empty?

  apply_claude_config(config)
end

.merged_configObject



31
32
33
# File 'lib/legion/llm/call/claude_config_loader.rb', line 31

def merged_config
  read_json(claude_settings_path).merge(read_json(claude_config_path))
end

.normalize_secret(value) ⇒ Object



160
161
162
163
164
165
166
167
168
# File 'lib/legion/llm/call/claude_config_loader.rb', line 160

def normalize_secret(value)
  return nil if value.nil?
  return value unless value.is_a?(String)

  normalized = value.strip
  return nil if normalized.empty?

  normalized
end

.oauth_account_available?Boolean

Returns:

  • (Boolean)


80
81
82
83
# File 'lib/legion/llm/call/claude_config_loader.rb', line 80

def 
  oauth = read_json(claude_config_path)[:oauthAccount]
  oauth.is_a?(Hash) && oauth.any? { |_k, value| !normalize_secret(value).nil? }
end

.openai_api_keyObject



52
53
54
55
56
57
58
59
# File 'lib/legion/llm/call/claude_config_loader.rb', line 52

def openai_api_key
  config = merged_config
  first_present(
    config[:openaiApiKey],
    config.dig(:env, :OPENAI_API_KEY),
    config.dig(:env, :CODEX_API_KEY)
  )
end

.read_json(path) ⇒ Object



35
36
37
38
39
40
41
42
# File 'lib/legion/llm/call/claude_config_loader.rb', line 35

def read_json(path)
  return {} unless File.exist?(path)

  ::JSON.parse(File.read(path), symbolize_names: true)
rescue StandardError => e
  handle_exception(e, level: :debug)
  {}
end

.resolve_setting_reference(value) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/legion/llm/call/claude_config_loader.rb', line 128

def resolve_setting_reference(value)
  case value
  when Array
    value.each do |entry|
      resolved = resolve_setting_reference(entry)
      return resolved unless resolved.nil?
    end
    nil
  when String
    resolved = normalize_secret(value)
    return nil if resolved.nil?

    if resolved.start_with?('env://')
      env_name = resolved.sub('env://', '')
      return normalize_secret(ENV.fetch(env_name, nil))
    end
    return nil if resolved.match?(SECRET_URI_PATTERN)

    resolved
  else
    normalize_secret(value)
  end
end

.setting_has_usable_credential?(value) ⇒ Boolean

Returns:

  • (Boolean)


124
125
126
# File 'lib/legion/llm/call/claude_config_loader.rb', line 124

def setting_has_usable_credential?(value)
  !resolve_setting_reference(value).nil?
end