atomcode_core/config/
provider.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
4pub struct ProviderConfig {
5 #[serde(rename = "type")]
6 pub provider_type: String,
7 #[serde(default, skip_serializing_if = "Option::is_none")]
8 pub api_key: Option<String>,
9 pub model: String,
10 pub base_url: Option<String>,
11 pub system_prompt: Option<String>,
12 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub user_agent: Option<String>,
16 #[serde(default = "default_context_window")]
21 pub context_window: usize,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub max_tokens: Option<usize>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub thinking_type: Option<String>,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub thinking_keep: Option<String>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub reasoning_history: Option<String>,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub thinking_enabled: Option<bool>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub thinking_budget: Option<u32>,
56 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
61 pub skip_tls_verify: bool,
62 #[serde(skip)]
65 pub ephemeral: bool,
66}
67
68impl ProviderConfig {
69 pub fn accepts_images(&self) -> bool {
75 crate::provider::model_name_suggests_vision(&self.model)
76 }
77}
78
79fn default_context_window() -> usize {
80 128000
81}
82
83pub fn default_context_window_for(provider_type: &str) -> usize {
84 match provider_type {
85 "ollama" => 8000,
86 _ => 128000,
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn accepts_images_false_for_text_only_model() {
96 let toml_str = r#"
100 type = "openai"
101 model = "GLM-5.1"
102 api_key = "sk-test"
103 base_url = "https://api-ai.gitcode.com/v1"
104 "#;
105 let cfg: ProviderConfig = toml::from_str(toml_str).expect("parse");
106 assert!(!cfg.accepts_images());
107 }
108
109 #[test]
110 fn accepts_images_true_via_heuristic_on_known_vision_model() {
111 let toml_str = r#"
112 type = "claude"
113 model = "claude-sonnet-4-5"
114 api_key = "sk-test"
115 "#;
116 let cfg: ProviderConfig = toml::from_str(toml_str).expect("parse");
117 assert!(cfg.accepts_images());
118 }
119
120 #[test]
121 fn thinking_fields_default_to_none() {
122 let toml_str = r#"
123 type = "claude"
124 model = "claude-sonnet-4"
125 base_url = "https://api.anthropic.com"
126 context_window = 128000
127 "#;
128 let cfg: ProviderConfig = toml::from_str(toml_str).expect("parse");
129 assert!(cfg.thinking_enabled.is_none());
130 assert!(cfg.thinking_budget.is_none());
131 }
132
133 #[test]
134 fn thinking_fields_parse_correctly() {
135 let toml_str = r#"
136 type = "claude"
137 model = "claude-sonnet-4"
138 base_url = "https://api.anthropic.com"
139 context_window = 128000
140 thinking_enabled = true
141 thinking_budget = 20000
142 "#;
143 let cfg: ProviderConfig = toml::from_str(toml_str).expect("parse");
144 assert_eq!(cfg.thinking_enabled, Some(true));
145 assert_eq!(cfg.thinking_budget, Some(20000));
146 }
147
148 #[test]
149 fn skip_tls_verify_defaults_to_false() {
150 let toml_str = r#"
151 type = "openai"
152 model = "gpt-4o"
153 api_key = "sk-test"
154 "#;
155 let cfg: ProviderConfig = toml::from_str(toml_str).expect("parse");
156 assert!(!cfg.skip_tls_verify, "skip_tls_verify should default to false");
157 }
158
159 #[test]
160 fn skip_tls_verify_can_be_set_true() {
161 let toml_str = r#"
162 type = "openai"
163 model = "gpt-4o"
164 api_key = "sk-test"
165 base_url = "https://self-signed.example.com/v1"
166 skip_tls_verify = true
167 "#;
168 let cfg: ProviderConfig = toml::from_str(toml_str).expect("parse");
169 assert!(cfg.skip_tls_verify, "skip_tls_verify should be true");
170 }
171
172 #[test]
173 fn skip_tls_verify_not_serialized_when_false() {
174 let cfg = ProviderConfig {
175 provider_type: "openai".into(),
176 api_key: Some("sk-test".into()),
177 model: "gpt-4o".into(),
178 base_url: None,
179 system_prompt: None,
180 user_agent: None,
181 context_window: 128000,
182 max_tokens: None,
183 thinking_type: None,
184 thinking_keep: None,
185 reasoning_history: None,
186 thinking_enabled: None,
187 thinking_budget: None,
188 skip_tls_verify: false,
189 ephemeral: false,
190
191};
192 let serialized = toml::to_string(&cfg).expect("serialize");
193 assert!(
194 !serialized.contains("skip_tls_verify"),
195 "skip_tls_verify should not be serialized when false"
196 );
197 }
198
199 #[test]
200 fn skip_tls_verify_serialized_when_true() {
201 let cfg = ProviderConfig {
202 provider_type: "openai".into(),
203 api_key: Some("sk-test".into()),
204 model: "gpt-4o".into(),
205 base_url: Some("https://self-signed.example.com/v1".into()),
206 system_prompt: None,
207 user_agent: None,
208 context_window: 128000,
209 max_tokens: None,
210 thinking_type: None,
211 thinking_keep: None,
212 reasoning_history: None,
213 thinking_enabled: None,
214 thinking_budget: None,
215 skip_tls_verify: true,
216 ephemeral: false,
217
218};
219 let serialized = toml::to_string(&cfg).expect("serialize");
220 assert!(
221 serialized.contains("skip_tls_verify = true"),
222 "skip_tls_verify should be serialized when true"
223 );
224 }
225}