Skip to main content

vtcode_config/core/
anthropic_speed.rs

1//! Native Claude speed selection and route eligibility.
2
3use serde::{Deserialize, Serialize};
4
5use crate::VTCodeConfig;
6use crate::constants::{env_vars, models::anthropic::normalize_model_id, urls};
7
8/// Native Anthropic Messages API speed. Unset configuration uses standard speed.
9#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
11#[serde(rename_all = "lowercase")]
12pub enum AnthropicSpeed {
13    Standard,
14    Fast,
15}
16
17impl AnthropicSpeed {
18    pub const fn as_str(self) -> &'static str {
19        match self {
20            Self::Standard => "standard",
21            Self::Fast => "fast",
22        }
23    }
24
25    /// Exact native IDs, including the existing dated snapshot normalization.
26    pub fn supports_model(model: &str) -> bool {
27        matches!(normalize_model_id(model), "claude-opus-5-5" | "claude-opus-5" | "claude-opus-4-8")
28    }
29
30    /// Preview models require the beta for either serialized speed value.
31    pub fn requires_beta(model: &str) -> bool {
32        matches!(normalize_model_id(model), "claude-opus-5" | "claude-opus-4-8")
33    }
34
35    /// Only the standard native Messages endpoint supports this integration.
36    pub fn is_native_endpoint(base_url: &str) -> bool {
37        let Ok(url) = url::Url::parse(base_url.trim()) else {
38            return false;
39        };
40        url.scheme() == "https"
41            && url.host_str() == Some("api.anthropic.com")
42            && url.port_or_known_default() == Some(443)
43            && url.username().is_empty()
44            && url.password().is_none()
45            && matches!(url.path(), "/v1" | "/v1/")
46            && url.query().is_none()
47            && url.fragment().is_none()
48    }
49
50    /// Explicit config wins over ANTHROPIC_BASE_URL, then the native default.
51    pub fn resolve_base_url(configured: Option<&str>) -> String {
52        configured
53            .map(str::trim)
54            .filter(|url| !url.is_empty())
55            .map(str::to_owned)
56            .or_else(|| {
57                std::env::var(env_vars::ANTHROPIC_BASE_URL)
58                    .ok()
59                    .map(|url| url.trim().to_owned())
60                    .filter(|url| !url.is_empty())
61            })
62            .unwrap_or_else(|| urls::ANTHROPIC_API_BASE.to_owned())
63    }
64}
65
66impl VTCodeConfig {
67    /// Mirror native dispatch eligibility for commands, picker, and display.
68    pub fn anthropic_speed_supported(&self, provider: &str, model: &str) -> bool {
69        vtcode_commons::provider::Provider::Anthropic.name_matches(provider)
70            && self.custom_provider(provider).is_none()
71            && AnthropicSpeed::supports_model(model)
72            && AnthropicSpeed::is_native_endpoint(&AnthropicSpeed::resolve_base_url(
73                self.provider_overrides
74                    .get("anthropic")
75                    .and_then(|config| config.base_url.as_deref()),
76            ))
77    }
78}
79
80#[cfg(test)]
81mod tests;