Skip to main content

vtcode_config/core/
provider.rs

1use serde::{Deserialize, Serialize};
2
3/// OpenAI-specific provider configuration
4#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
5#[derive(Debug, Clone, Deserialize, Serialize, Default)]
6pub struct OpenAIConfig {
7    /// Enable Responses API WebSocket transport for non-streaming requests.
8    /// This is an opt-in path designed for long-running, tool-heavy workflows.
9    #[serde(default)]
10    pub websocket_mode: bool,
11
12    /// Optional Responses API `store` flag.
13    /// Set to `false` to avoid server-side storage when using Responses-compatible models.
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub responses_store: Option<bool>,
16
17    /// Optional Responses API `include` selectors.
18    /// Example: `["reasoning.encrypted_content"]` for encrypted reasoning continuity.
19    #[serde(default, skip_serializing_if = "Vec::is_empty")]
20    pub responses_include: Vec<String>,
21}
22
23/// Anthropic-specific provider configuration
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
25#[derive(Debug, Clone, Deserialize, Serialize)]
26pub struct AnthropicConfig {
27    /// DEPRECATED: Model name validation has been removed. The Anthropic API validates
28    /// model names directly, avoiding maintenance burden and allowing flexibility.
29    /// This field is kept for backward compatibility but has no effect.
30    #[deprecated(
31        since = "0.75.0",
32        note = "Model validation removed. API validates model names directly."
33    )]
34    #[serde(default)]
35    pub skip_model_validation: bool,
36
37    /// Enable extended thinking feature for Anthropic models
38    /// When enabled, Claude uses internal reasoning before responding, providing
39    /// enhanced reasoning capabilities for complex tasks.
40    /// Only supported by Claude 4, Claude 4.5, and Claude 3.7 Sonnet models.
41    /// Claude 4.6 uses adaptive thinking instead of extended thinking.
42    /// Note: Extended thinking is now auto-enabled by default (31,999 tokens).
43    /// Set MAX_THINKING_TOKENS=63999 environment variable for 2x budget on 64K models.
44    /// See: https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
45    #[serde(default = "default_extended_thinking_enabled")]
46    pub extended_thinking_enabled: bool,
47
48    /// Beta header for interleaved thinking feature
49    #[serde(default = "default_interleaved_thinking_beta")]
50    pub interleaved_thinking_beta: String,
51
52    /// Budget tokens for extended thinking (minimum: 1024, default: 31999)
53    /// On 64K output models (Opus 4.5, Sonnet 4.5, Haiku 4.5): default 31,999, max 63,999
54    /// On 32K output models (Opus 4): max 31,999
55    /// Use MAX_THINKING_TOKENS environment variable to override.
56    #[serde(default = "default_interleaved_thinking_budget_tokens")]
57    pub interleaved_thinking_budget_tokens: u32,
58
59    /// Type value for enabling interleaved thinking
60    #[serde(default = "default_interleaved_thinking_type")]
61    pub interleaved_thinking_type_enabled: String,
62
63    /// Tool search configuration for dynamic tool discovery (advanced-tool-use beta)
64    #[serde(default)]
65    pub tool_search: ToolSearchConfig,
66
67    /// Effort level for token usage (high, medium, low)
68    /// Controls how many tokens Claude uses when responding, trading off between
69    /// response thoroughness and token efficiency.
70    /// Supported by Claude Opus 4.5/4.6 (4.5 requires effort beta header)
71    #[serde(default = "default_effort")]
72    pub effort: String,
73
74    /// Enable token counting via the count_tokens endpoint
75    /// When enabled, the agent can estimate input token counts before making API calls
76    /// Useful for proactive management of rate limits and costs
77    #[serde(default = "default_count_tokens_enabled")]
78    pub count_tokens_enabled: bool,
79}
80
81#[allow(deprecated)]
82impl Default for AnthropicConfig {
83    fn default() -> Self {
84        Self {
85            skip_model_validation: false,
86            extended_thinking_enabled: default_extended_thinking_enabled(),
87            interleaved_thinking_beta: default_interleaved_thinking_beta(),
88            interleaved_thinking_budget_tokens: default_interleaved_thinking_budget_tokens(),
89            interleaved_thinking_type_enabled: default_interleaved_thinking_type(),
90            tool_search: ToolSearchConfig::default(),
91            effort: default_effort(),
92            count_tokens_enabled: default_count_tokens_enabled(),
93        }
94    }
95}
96
97#[inline]
98fn default_count_tokens_enabled() -> bool {
99    false
100}
101
102/// Configuration for Anthropic's tool search feature (advanced-tool-use beta)
103/// Enables dynamic tool discovery for large tool catalogs (up to 10k tools)
104#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
105#[derive(Debug, Clone, Deserialize, Serialize)]
106pub struct ToolSearchConfig {
107    /// Enable tool search feature (requires advanced-tool-use-2025-11-20 beta)
108    #[serde(default)]
109    pub enabled: bool,
110
111    /// Search algorithm: "regex" (Python regex patterns) or "bm25" (natural language)
112    #[serde(default = "default_tool_search_algorithm")]
113    pub algorithm: String,
114
115    /// Automatically defer loading of all tools except core tools
116    #[serde(default = "default_defer_by_default")]
117    pub defer_by_default: bool,
118
119    /// Maximum number of tool search results to return
120    #[serde(default = "default_max_results")]
121    pub max_results: u32,
122
123    /// Tool names that should never be deferred (always available)
124    #[serde(default)]
125    pub always_available_tools: Vec<String>,
126}
127
128impl Default for ToolSearchConfig {
129    fn default() -> Self {
130        Self {
131            enabled: false,
132            algorithm: default_tool_search_algorithm(),
133            defer_by_default: default_defer_by_default(),
134            max_results: default_max_results(),
135            always_available_tools: vec![],
136        }
137    }
138}
139
140#[inline]
141fn default_tool_search_algorithm() -> String {
142    "regex".to_string()
143}
144
145#[inline]
146fn default_defer_by_default() -> bool {
147    true
148}
149
150#[inline]
151fn default_max_results() -> u32 {
152    5
153}
154
155#[inline]
156fn default_extended_thinking_enabled() -> bool {
157    true
158}
159
160#[inline]
161fn default_interleaved_thinking_beta() -> String {
162    "interleaved-thinking-2025-05-14".to_string()
163}
164
165#[inline]
166fn default_interleaved_thinking_budget_tokens() -> u32 {
167    31999
168}
169
170#[inline]
171fn default_interleaved_thinking_type() -> String {
172    "enabled".to_string()
173}
174
175#[inline]
176fn default_effort() -> String {
177    "low".to_string()
178}
179
180#[cfg(test)]
181mod tests {
182    use super::OpenAIConfig;
183
184    #[test]
185    fn openai_config_defaults_to_websocket_mode_disabled() {
186        let config = OpenAIConfig::default();
187        assert!(!config.websocket_mode);
188        assert_eq!(config.responses_store, None);
189        assert!(config.responses_include.is_empty());
190    }
191
192    #[test]
193    fn openai_config_parses_websocket_mode_opt_in() {
194        let parsed: OpenAIConfig =
195            toml::from_str("websocket_mode = true").expect("config should parse");
196        assert!(parsed.websocket_mode);
197        assert_eq!(parsed.responses_store, None);
198        assert!(parsed.responses_include.is_empty());
199    }
200
201    #[test]
202    fn openai_config_parses_responses_options() {
203        let parsed: OpenAIConfig = toml::from_str(
204            r#"
205responses_store = false
206responses_include = ["reasoning.encrypted_content", "output_text.annotations"]
207"#,
208        )
209        .expect("config should parse");
210        assert_eq!(parsed.responses_store, Some(false));
211        assert_eq!(
212            parsed.responses_include,
213            vec![
214                "reasoning.encrypted_content".to_string(),
215                "output_text.annotations".to_string()
216            ]
217        );
218    }
219}