Module: Kward::ModelInfo
- Defined in:
- lib/kward/model/model_info.rb
Overview
Static and configured model metadata helpers.
Constant Summary collapse
- DEFAULT_OPENAI_MODEL =
"gpt-5.5"- DEFAULT_OPENROUTER_MODEL =
"openai/gpt-5.5"- DEFAULT_COPILOT_MODEL =
"gpt-5-mini"- DEFAULT_ANTHROPIC_MODEL =
"claude-sonnet-4-6"- DEFAULT_REASONING_EFFORT =
"medium"- OPENAI_MODEL_CHOICES =
%w[gpt-5.5 gpt-5.4 gpt-5.4-mini gpt-5.3-codex-spark].freeze
- OPENROUTER_MODEL_CHOICES =
OPENAI_MODEL_CHOICES.map { |model| "openai/#{model}" }.freeze
- ANTHROPIC_MODEL_CHOICES =
%w[ claude-opus-4-8 claude-sonnet-4-6 claude-haiku-4-5 claude-opus-4-7 claude-opus-4-6 claude-opus-4-5 claude-sonnet-4-5 ].freeze
- COPILOT_MODEL_CHOICES =
%w[ gpt-5-mini gpt-5.3-codex gpt-5.4 gpt-5.4-mini gpt-5.5 claude-haiku-4.5 claude-opus-4.5 claude-opus-4.7 claude-opus-4.8 claude-sonnet-4.5 claude-sonnet-4.6 gemini-2.5-pro gemini-3-flash-preview gemini-3.1-pro-preview gemini-3.5-flash oswe-vscode-prime ].freeze
- REASONING_EFFORT_CHOICES =
[ ["low", "Low"], ["medium", "Medium"], ["high", "High"], ["xhigh", "Extra High"] ].freeze
- OPENAI_REASONING_EFFORT_CHOICES =
[ ["none", "None"], *REASONING_EFFORT_CHOICES ].freeze
- ANTHROPIC_HIGH_REASONING_EFFORT_CHOICES =
[ ["low", "Low"], ["medium", "Medium"], ["high", "High"], ["xhigh", "Extra High"], ["max", "Max"] ].freeze
- ANTHROPIC_STANDARD_REASONING_EFFORT_CHOICES =
[ ["low", "Low"], ["medium", "Medium"], ["high", "High"], ["max", "Max"] ].freeze
- ANTHROPIC_OPUS_4_5_REASONING_EFFORT_CHOICES =
[ ["low", "Low"], ["medium", "Medium"], ["high", "High"] ].freeze
- IMAGE_UNSUPPORTED_MODELS =
[ /(?:\A|\/)gpt-5\.3-codex-spark\z/ ].freeze
- CODEX_CONTEXT_WINDOWS =
[ [/\Agpt-5\.5/, 400_000], [/\Agpt-5\.4-mini/, 400_000], [/\Agpt-5\.4/, 1_050_000], [/\Agpt-5-mini/, 400_000], [/\Agpt-5-codex/, 400_000], [/\Agpt-5\.3-codex-spark/, 128_000], [/\Agpt-5\.3-codex/, 400_000], [/\Agpt-5\.2-codex/, 400_000], [/\Agpt-5/, 400_000], [/\Agpt-4\.1/, 1_047_576], [/\Agpt-4o/, 128_000], [/\Ao3/, 200_000], [/\Ao4/, 200_000], [/\Agpt-4/, 128_000], [/\Agpt-3\.5-turbo/, 16_385] ].freeze
- OPENAI_CONTEXT_WINDOWS =
[ [/\Agpt-5\.5/, 1_050_000], [/\Agpt-5\.4-mini/, 400_000], [/\Agpt-5\.4/, 1_050_000], [/\Agpt-5-mini/, 400_000], [/\Agpt-5-codex/, 400_000], [/\Agpt-5\.3-codex-spark/, 128_000], [/\Agpt-5\.3-codex/, 400_000], [/\Agpt-5\.2-codex/, 400_000], [/\Agpt-5/, 400_000], [/\Agpt-4\.1/, 1_047_576], [/\Agpt-4o/, 128_000], [/\Ao3/, 200_000], [/\Ao4/, 200_000], [/\Agpt-4/, 128_000], [/\Agpt-3\.5-turbo/, 16_385] ].freeze
- ANTHROPIC_CONTEXT_WINDOWS =
[ [/\Aclaude-(?:fable|mythos)-5(?:\z|-)/, 1_000_000], [/\Aclaude-opus-4-(?:6|7|8)(?:\z|-)/, 1_000_000], [/\Aclaude-sonnet-4-6(?:\z|-)/, 1_000_000], [/\Aclaude-(?:haiku|opus|sonnet)-4-5(?:\z|-)/, 200_000], [/\Aclaude-(?:haiku|opus|sonnet)-4(?:\z|-)/, 200_000] ].freeze
- GEMINI_CONTEXT_WINDOWS =
[ [/\Agemini-(?:2\.5-pro|3(?:\.1)?-pro|3(?:\.5)?-flash)/, 1_048_576] ].freeze
Class Method Summary collapse
- .anthropic_context_window(id) ⇒ Object
- .anthropic_reasoning_effort_choices(id) ⇒ Object
- .boolean_value(value, default: false) ⇒ Object
- .config_key_for_provider(provider) ⇒ Object
- .config_provider_for_provider(provider) ⇒ Object
- .config_values_for_selection(provider, model) ⇒ Object
- .context_window(provider, id) ⇒ Object
- .copilot_context_window(id) ⇒ Object
- .model_for(provider, config:, override_model: nil, env: ENV) ⇒ Object
- .normalize(model, current_provider: nil, current_model: nil, current_reasoning_effort: nil) ⇒ Object
- .normalize_anthropic_model(id) ⇒ Object
- .normalize_copilot_model(id) ⇒ Object
- .openai_reasoning_effort_choices(id) ⇒ Object
- .openrouter_context_window(id) ⇒ Object
- .pattern_context_window(patterns, id) ⇒ Object
- .provider_config_value(provider) ⇒ Object
- .provider_label(provider) ⇒ Object
- .reasoning_config_key_for_provider(provider) ⇒ Object
- .reasoning_effort(config:, env: ENV, provider: nil) ⇒ Object
- .reasoning_effort_choices(provider, id) ⇒ Object
- .reasoning_supported?(provider, id) ⇒ Boolean
- .stringify_keys(value) ⇒ Object
- .supports_images?(_provider, id) ⇒ Boolean
Class Method Details
.anthropic_context_window(id) ⇒ Object
265 266 267 |
# File 'lib/kward/model/model_info.rb', line 265 def anthropic_context_window(id) pattern_context_window(ANTHROPIC_CONTEXT_WINDOWS, normalize_anthropic_model(id)) end |
.anthropic_reasoning_effort_choices(id) ⇒ Object
306 307 308 309 310 311 312 313 |
# File 'lib/kward/model/model_info.rb', line 306 def anthropic_reasoning_effort_choices(id) text = normalize_anthropic_model(id) return ANTHROPIC_HIGH_REASONING_EFFORT_CHOICES if text.match?(/\Aclaude-(?:fable|mythos)-5(?:\z|-)|\Aclaude-opus-4-(?:7|8)(?:\z|-)/) return ANTHROPIC_STANDARD_REASONING_EFFORT_CHOICES if text.match?(/\Aclaude-(?:opus-4-6|sonnet-4-6)(?:\z|-)/) return ANTHROPIC_OPUS_4_5_REASONING_EFFORT_CHOICES if text.match?(/\Aclaude-opus-4-5(?:\z|-)/) [] end |
.boolean_value(value, default: false) ⇒ Object
332 333 334 335 336 337 |
# File 'lib/kward/model/model_info.rb', line 332 def boolean_value(value, default: false) return default if value.nil? return value if value == true || value == false value.to_s == "true" end |
.config_key_for_provider(provider) ⇒ Object
202 203 204 205 206 207 208 209 |
# File 'lib/kward/model/model_info.rb', line 202 def config_key_for_provider(provider) case provider.to_s.downcase when "openrouter" then "openrouter_model" when "copilot" then "copilot_model" when "anthropic", "claude" then "anthropic_model" else "openai_model" end end |
.config_provider_for_provider(provider) ⇒ Object
220 221 222 |
# File 'lib/kward/model/model_info.rb', line 220 def config_provider_for_provider(provider) provider_config_value(provider) end |
.config_values_for_selection(provider, model) ⇒ Object
224 225 226 227 228 229 |
# File 'lib/kward/model/model_info.rb', line 224 def config_values_for_selection(provider, model) { config_key_for_provider(provider) => model, "provider" => config_provider_for_provider(provider) } end |
.context_window(provider, id) ⇒ Object
231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/kward/model/model_info.rb', line 231 def context_window(provider, id) case provider when "Codex" pattern_context_window(CODEX_CONTEXT_WINDOWS, id) when "OpenRouter" openrouter_context_window(id) when "Copilot" copilot_context_window(id) when "Anthropic" anthropic_context_window(id) end end |
.copilot_context_window(id) ⇒ Object
258 259 260 261 262 263 |
# File 'lib/kward/model/model_info.rb', line 258 def copilot_context_window(id) text = id.to_s pattern_context_window(OPENAI_CONTEXT_WINDOWS, text) || anthropic_context_window(normalize_anthropic_model(text)) || pattern_context_window(GEMINI_CONTEXT_WINDOWS, text) end |
.model_for(provider, config:, override_model: nil, env: ENV) ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/kward/model/model_info.rb', line 121 def model_for(provider, config:, override_model: nil, env: ENV) return override_model if override_model case provider when "OpenRouter" env["OPENROUTER_MODEL"] || ConfigFiles.config_value(config, "openrouter_model", "model") || DEFAULT_OPENROUTER_MODEL when "Copilot" normalize_copilot_model(env["COPILOT_MODEL"] || ConfigFiles.config_value(config, "copilot_model", "model") || DEFAULT_COPILOT_MODEL) when "Anthropic" normalize_anthropic_model(env["ANTHROPIC_MODEL"] || ConfigFiles.config_value(config, "anthropic_model", "model") || DEFAULT_ANTHROPIC_MODEL) else env["OPENAI_MODEL"] || ConfigFiles.config_value(config, "openai_model", "model") || DEFAULT_OPENAI_MODEL end end |
.normalize(model, current_provider: nil, current_model: nil, current_reasoning_effort: nil) ⇒ Object
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/kward/model/model_info.rb', line 315 def normalize(model, current_provider: nil, current_model: nil, current_reasoning_effort: nil) model = stringify_keys(model || {}) provider = model["provider"] id = model["id"] || model["model"] reasoning = boolean_value(model["reasoning"], default: reasoning_supported?(provider, id)) reasoning_effort = model["reasoningEffort"] || model["reasoning_effort"] || (current_reasoning_effort if reasoning) { provider: provider, id: id, name: model["name"] || id, reasoning: reasoning, reasoningEffort: reasoning_effort, contextWindow: model["contextWindow"] || model["context_window"] || context_window(provider, id), current: boolean_value(model["current"], default: provider == current_provider && id == current_model) }.compact end |
.normalize_anthropic_model(id) ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/kward/model/model_info.rb', line 136 def normalize_anthropic_model(id) text = id.to_s.strip return DEFAULT_ANTHROPIC_MODEL if text.empty? text = text.delete_prefix("anthropic/").delete_prefix("claude/") { "claude-sonnet-4.6" => "claude-sonnet-4-6", "claude-sonnet-4.5" => "claude-sonnet-4-5", "claude-opus-4.8" => "claude-opus-4-8", "claude-opus-4.7" => "claude-opus-4-7", "claude-opus-4.6" => "claude-opus-4-6", "claude-opus-4.5" => "claude-opus-4-5", "claude-haiku-4.5" => "claude-haiku-4-5" }.fetch(text, text) end |
.normalize_copilot_model(id) ⇒ Object
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/kward/model/model_info.rb', line 151 def normalize_copilot_model(id) text = id.to_s.strip return DEFAULT_COPILOT_MODEL if text.empty? text = text.delete_prefix("copilot/") { "claude-haiku-4-5" => "claude-haiku-4.5", "claude-opus-4-5" => "claude-opus-4.5", "claude-opus-4-6" => "claude-opus-4.6", "claude-opus-4-6-fast" => "claude-opus-4.6-fast", "claude-opus-4-7" => "claude-opus-4.7", "claude-opus-4-8" => "claude-opus-4.8", "claude-sonnet-4-5" => "claude-sonnet-4.5", "claude-sonnet-4-6" => "claude-sonnet-4.6", "gemini-3.1-pro" => "gemini-3.1-pro-preview", "gemini-3-flash" => "gemini-3-flash-preview" }.fetch(text, text) end |
.openai_reasoning_effort_choices(id) ⇒ Object
299 300 301 302 303 304 |
# File 'lib/kward/model/model_info.rb', line 299 def openai_reasoning_effort_choices(id) text = id.to_s.delete_prefix("openai/") return REASONING_EFFORT_CHOICES if text.match?(/\Agpt-5\.[23]-codex/) OPENAI_REASONING_EFFORT_CHOICES end |
.openrouter_context_window(id) ⇒ Object
249 250 251 252 253 254 255 256 |
# File 'lib/kward/model/model_info.rb', line 249 def openrouter_context_window(id) text = id.to_s return pattern_context_window(OPENAI_CONTEXT_WINDOWS, text.delete_prefix("openai/")) if text.start_with?("openai/") return anthropic_context_window(text.delete_prefix("anthropic/")) if text.start_with?("anthropic/") return pattern_context_window(GEMINI_CONTEXT_WINDOWS, text.delete_prefix("google/")) if text.start_with?("google/") nil end |
.pattern_context_window(patterns, id) ⇒ Object
244 245 246 247 |
# File 'lib/kward/model/model_info.rb', line 244 def pattern_context_window(patterns, id) match = patterns.find { |pattern, _window| id.to_s.match?(pattern) } match&.last end |
.provider_config_value(provider) ⇒ Object
193 194 195 196 197 198 199 200 |
# File 'lib/kward/model/model_info.rb', line 193 def provider_config_value(provider) case provider.to_s.downcase when "openrouter" then "openrouter" when "copilot" then "copilot" when "anthropic", "claude" then "anthropic" else "codex" end end |
.provider_label(provider) ⇒ Object
183 184 185 186 187 188 189 190 191 |
# File 'lib/kward/model/model_info.rb', line 183 def provider_label(provider) case provider.to_s.downcase when "openrouter" then "OpenRouter" when "copilot" then "Copilot" when "anthropic", "claude" then "Anthropic" when "codex", "openai" then "Codex" else provider.to_s end end |
.reasoning_config_key_for_provider(provider) ⇒ Object
211 212 213 214 215 216 217 218 |
# File 'lib/kward/model/model_info.rb', line 211 def reasoning_config_key_for_provider(provider) case provider.to_s.downcase when "openrouter" then "openrouter_reasoning_effort" when "copilot" then "copilot_reasoning_effort" when "anthropic", "claude" then "anthropic_reasoning_effort" else "openai_reasoning_effort" end end |
.reasoning_effort(config:, env: ENV, provider: nil) ⇒ Object
170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/kward/model/model_info.rb', line 170 def reasoning_effort(config:, env: ENV, provider: nil) case provider.to_s when "OpenRouter" env["OPENROUTER_REASONING_EFFORT"] || ConfigFiles.config_value(config, "openrouter_reasoning_effort", "reasoning_effort", "thinking_level") || DEFAULT_REASONING_EFFORT when "Copilot" env["COPILOT_REASONING_EFFORT"] || ConfigFiles.config_value(config, "copilot_reasoning_effort", "reasoning_effort", "thinking_level") || DEFAULT_REASONING_EFFORT when "Anthropic" env["ANTHROPIC_REASONING_EFFORT"] || ConfigFiles.config_value(config, "anthropic_reasoning_effort", "reasoning_effort", "thinking_level") || DEFAULT_REASONING_EFFORT else env["OPENAI_REASONING_EFFORT"] || ConfigFiles.config_value(config, "openai_reasoning_effort", "reasoning_effort", "thinking_level") || DEFAULT_REASONING_EFFORT end end |
.reasoning_effort_choices(provider, id) ⇒ Object
286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/kward/model/model_info.rb', line 286 def reasoning_effort_choices(provider, id) case provider when "Codex", "OpenRouter" openai_reasoning_effort_choices(id) when "Anthropic" anthropic_reasoning_effort_choices(id) when "Copilot" id.to_s.match?(/\Agpt-5(?:\.|-|\z)/) ? openai_reasoning_effort_choices(id) : [] else [] end end |
.reasoning_supported?(provider, id) ⇒ Boolean
273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/kward/model/model_info.rb', line 273 def reasoning_supported?(provider, id) case provider when "Codex", "OpenRouter" true when "Anthropic" !reasoning_effort_choices(provider, id).empty? when "Copilot" id.to_s.match?(/\Agpt-5(?:\.|-|\z)/) else false end end |
.stringify_keys(value) ⇒ Object
339 340 341 |
# File 'lib/kward/model/model_info.rb', line 339 def stringify_keys(value) value.each_with_object({}) { |(key, item), result| result[key.to_s] = item } end |
.supports_images?(_provider, id) ⇒ Boolean
269 270 271 |
# File 'lib/kward/model/model_info.rb', line 269 def supports_images?(_provider, id) IMAGE_UNSUPPORTED_MODELS.none? { |pattern| id.to_s.match?(pattern) } end |