Skip to main content

vtcode_config/models/
provider.rs

1//! Provider extension methods that depend on vtcode-config model catalogs.
2//!
3//! The `Provider` enum and its core methods are defined in `vtcode-commons`.
4//! This module adds vtcode-config-specific extension methods via the
5//! [`ProviderModelSupport`] trait.
6
7pub use vtcode_commons::provider::Provider;
8
9use super::{ModelId, model_catalog_entry};
10use crate::core::OpenAIServiceTier;
11use std::str::FromStr;
12
13const GENERIC_REASONING_EFFORTS: &[&str] = &["low", "medium", "high"];
14
15/// Extension trait on `Provider` for model-specific capability queries.
16///
17/// These methods require vtcode-config model catalogs and constants,
18/// so they cannot live in vtcode-commons with the core `Provider` type.
19pub trait ProviderModelSupport: AsRef<str> {
20    /// Determine if the provider/model exposes structured reasoning output.
21    ///
22    /// Catalog metadata is authoritative for curated models. Unknown routes
23    /// retain the provider's legacy capability fallback so custom endpoints
24    /// continue to work without a catalog entry.
25    fn supports_reasoning(&self, model: &str) -> bool {
26        model_catalog_entry(self.as_ref(), model)
27            .map(|entry| entry.reasoning)
28            .unwrap_or_else(|| self.supports_reasoning_effort(model))
29    }
30
31    /// Determine if the provider supports configurable reasoning effort for the model.
32    fn supports_reasoning_effort(&self, model: &str) -> bool;
33
34    /// Exact effort levels accepted by the provider/model route.
35    fn supported_reasoning_efforts(&self, model: &str) -> &'static [&'static str] {
36        if let Some(entry) = model_catalog_entry(self.as_ref(), model) {
37            return entry.reasoning_efforts;
38        }
39        if self.supports_reasoning_effort(model) {
40            GENERIC_REASONING_EFFORTS
41        } else {
42            &[]
43        }
44    }
45
46    /// Determine if the provider supports the `service_tier` request parameter.
47    fn supports_service_tier(&self, model: &str) -> bool;
48
49    /// Determine if the provider supports a specific `service_tier` value.
50    /// `ultrafast` is a native-OpenAI-only tier: every other provider,
51    /// including OpenAI-compatible gateways that omit it wire-side, reports
52    /// false even when it accepts `flex`/`priority`.
53    fn supports_service_tier_value(&self, model: &str, tier: OpenAIServiceTier) -> bool {
54        match tier {
55            OpenAIServiceTier::Ultrafast => {
56                self.as_ref() == Provider::OpenAI.as_ref() && self.supports_service_tier(model)
57            }
58            _ => self.supports_service_tier(model),
59        }
60    }
61}
62
63impl ProviderModelSupport for Provider {
64    fn supports_reasoning_effort(&self, model: &str) -> bool {
65        if let Some(entry) = model_catalog_entry(self.as_ref(), model) {
66            // Curated metadata is the single source of truth for known routes.
67            // This keeps newly generated catalog entries from depending on a
68            // second hand-maintained provider table and blocks effort payloads
69            // when a model only exposes structured reasoning.
70            return !entry.reasoning_efforts.is_empty();
71        }
72
73        use crate::constants::models;
74
75        match self {
76            Provider::Gemini => models::google::REASONING_MODELS.contains(&model),
77            Provider::OpenAI => models::openai::REASONING_MODELS.contains(&model),
78            Provider::Anthropic => models::anthropic::REASONING_MODELS.contains(&model),
79            Provider::Copilot => false,
80            Provider::DeepSeek => model == models::deepseek::DEEPSEEK_FLASH || model == "deepseek-reasoner",
81            Provider::Meta => models::meta::REASONING_MODELS.contains(&model),
82            Provider::OpenRouter => {
83                if let Ok(model_id) = ModelId::from_str(model) {
84                    if let Some(meta) = crate::models::openrouter_generated::metadata_for(model_id) {
85                        return meta.reasoning;
86                    }
87                    return false;
88                }
89                models::openrouter::REASONING_MODELS.contains(&model)
90            }
91            Provider::Ollama => models::ollama::REASONING_LEVEL_MODELS.contains(&model),
92            Provider::OllamaCloud => models::ollama::REASONING_LEVEL_MODELS.contains(&model),
93            Provider::LmStudio => models::lmstudio::REASONING_MODELS.contains(&model),
94            Provider::LlamaCpp => models::llamacpp::REASONING_MODELS.contains(&model),
95            Provider::Moonshot => models::moonshot::REASONING_MODELS.contains(&model),
96            Provider::ZAI => models::zai::REASONING_MODELS.contains(&model),
97            Provider::Minimax => models::minimax::SUPPORTED_MODELS.contains(&model),
98            Provider::MiMo => models::mimo::SUPPORTED_MODELS.contains(&model),
99            Provider::Mistral => models::mistral::SUPPORTED_MODELS.contains(&model),
100            Provider::HuggingFace => models::huggingface::REASONING_MODELS.contains(&model),
101            Provider::OpenCodeZen => {
102                if models::opencode_zen::OPENAI_MODELS.contains(&model) {
103                    Provider::OpenAI.supports_reasoning_effort(model)
104                } else if models::opencode_zen::ANTHROPIC_MODELS.contains(&model) {
105                    Provider::Anthropic.supports_reasoning_effort(model)
106                } else {
107                    false
108                }
109            }
110            Provider::OpenCodeGo => false,
111            Provider::Qwen => models::qwen::REASONING_MODELS.contains(&model),
112            Provider::StepFun => models::stepfun::REASONING_MODELS.contains(&model),
113            Provider::Evolink => models::evolink::REASONING_MODELS.contains(&model),
114            Provider::Poolside => false,
115            Provider::Vercel => !models::vercel::NON_REASONING_MODELS.contains(&model),
116            Provider::XAI => models::xai::REASONING_MODELS.contains(&model),
117            Provider::NVIDIA => models::nvidia::REASONING_MODELS.contains(&model),
118            Provider::MergeGateway => models::merge_gateway::route_supports_reasoning(model),
119        }
120    }
121
122    fn supports_service_tier(&self, model: &str) -> bool {
123        use crate::constants::models;
124
125        match self {
126            Provider::OpenAI => models::openai::SERVICE_TIER_MODELS.contains(&model),
127            Provider::MergeGateway => model.trim().starts_with("openai/"),
128            // All other OpenAI-compatible wire providers accept `service_tier`
129            // (including `ultrafast`); native non-OpenAI APIs do not.
130            Provider::DeepSeek
131            | Provider::Meta
132            | Provider::OpenRouter
133            | Provider::Ollama
134            | Provider::OllamaCloud
135            | Provider::LmStudio
136            | Provider::LlamaCpp
137            | Provider::Moonshot
138            | Provider::ZAI
139            | Provider::Minimax
140            | Provider::MiMo
141            | Provider::Mistral
142            | Provider::HuggingFace
143            | Provider::OpenCodeZen
144            | Provider::OpenCodeGo
145            | Provider::Qwen
146            | Provider::StepFun
147            | Provider::Evolink
148            | Provider::Poolside
149            | Provider::XAI
150            | Provider::NVIDIA
151            | Provider::Vercel => true,
152            Provider::Gemini | Provider::Anthropic | Provider::Copilot => false,
153        }
154    }
155}