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
- 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
248 249 250 |
# File 'lib/kward/model/model_info.rb', line 248 def anthropic_context_window(id) pattern_context_window(ANTHROPIC_CONTEXT_WINDOWS, normalize_anthropic_model(id)) end |
.anthropic_reasoning_effort_choices(id) ⇒ Object
289 290 291 292 293 294 295 296 |
# File 'lib/kward/model/model_info.rb', line 289 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
315 316 317 318 319 320 |
# File 'lib/kward/model/model_info.rb', line 315 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
185 186 187 188 189 190 191 192 |
# File 'lib/kward/model/model_info.rb', line 185 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
203 204 205 |
# File 'lib/kward/model/model_info.rb', line 203 def config_provider_for_provider(provider) provider_config_value(provider) end |
.config_values_for_selection(provider, model) ⇒ Object
207 208 209 210 211 212 |
# File 'lib/kward/model/model_info.rb', line 207 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
214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/kward/model/model_info.rb', line 214 def context_window(provider, id) case provider when "Codex" pattern_context_window(OPENAI_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
241 242 243 244 245 246 |
# File 'lib/kward/model/model_info.rb', line 241 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
104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/kward/model/model_info.rb', line 104 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
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/kward/model/model_info.rb', line 298 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
119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/kward/model/model_info.rb', line 119 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
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/kward/model/model_info.rb', line 134 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
282 283 284 285 286 287 |
# File 'lib/kward/model/model_info.rb', line 282 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
232 233 234 235 236 237 238 239 |
# File 'lib/kward/model/model_info.rb', line 232 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
227 228 229 230 |
# File 'lib/kward/model/model_info.rb', line 227 def pattern_context_window(patterns, id) match = patterns.find { |pattern, _window| id.to_s.match?(pattern) } match&.last end |
.provider_config_value(provider) ⇒ Object
176 177 178 179 180 181 182 183 |
# File 'lib/kward/model/model_info.rb', line 176 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
166 167 168 169 170 171 172 173 174 |
# File 'lib/kward/model/model_info.rb', line 166 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
194 195 196 197 198 199 200 201 |
# File 'lib/kward/model/model_info.rb', line 194 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
153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/kward/model/model_info.rb', line 153 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
269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/kward/model/model_info.rb', line 269 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
256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/kward/model/model_info.rb', line 256 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
322 323 324 |
# File 'lib/kward/model/model_info.rb', line 322 def stringify_keys(value) value.each_with_object({}) { |(key, item), result| result[key.to_s] = item } end |
.supports_images?(_provider, id) ⇒ Boolean
252 253 254 |
# File 'lib/kward/model/model_info.rb', line 252 def supports_images?(_provider, id) IMAGE_UNSUPPORTED_MODELS.none? { |pattern| id.to_s.match?(pattern) } end |