1use super::args::{Cli, ModelCommands};
4use crate::llm::factory::{create_provider_with_config, get_factory};
5use crate::utils::dot_config::{DotConfig, get_dot_manager, load_user_config};
6use anyhow::{Result, anyhow};
7use colored::*;
8
9pub async fn handle_models_command(cli: &Cli, command: &ModelCommands) -> Result<()> {
11 match command {
12 ModelCommands::List => handle_list_models(cli).await,
13 ModelCommands::SetProvider { provider } => handle_set_provider(cli, provider).await,
14 ModelCommands::SetModel { model } => handle_set_model(cli, model).await,
15 ModelCommands::Config {
16 provider,
17 api_key,
18 base_url,
19 model,
20 } => {
21 handle_config_provider(
22 cli,
23 provider,
24 api_key.as_deref(),
25 base_url.as_deref(),
26 model.as_deref(),
27 )
28 .await
29 }
30 ModelCommands::Test { provider } => handle_test_provider(cli, provider).await,
31 ModelCommands::Compare => handle_compare_models(cli).await,
32 ModelCommands::Info { model } => handle_model_info(cli, model).await,
33 }
34}
35
36async fn handle_list_models(_cli: &Cli) -> Result<()> {
38 println!("{}", "Available Providers & Models".bold().underline());
39 println!();
40
41 let factory = get_factory().lock().unwrap();
42 let config = load_user_config().unwrap_or_default();
43 let providers = factory.list_providers();
44
45 for provider_name in &providers {
46 let is_current = config.preferences.default_provider == *provider_name;
47 let status = if is_current { "✦" } else { " " };
48 let provider_display = format!("{}{}", status, provider_name.to_uppercase());
49
50 let colored_provider = if is_current {
52 format!("{}", provider_display.bold().green())
53 } else {
54 format!("{}", provider_display.bold())
55 };
56 println!("{}", colored_provider);
57
58 if let Ok(provider) =
60 create_provider_with_config(provider_name, Some("dummy".to_string()), None, None, None)
61 {
62 let models = provider.supported_models();
63 let current_model = &config.preferences.default_model;
64
65 for model in models.iter().take(3) {
66 let is_current_model = current_model == model;
68 let model_status = if is_current_model { "⭐" } else { " " };
69 let colored_model = if is_current_model {
70 format!("{}", model.clone().bold().cyan())
71 } else {
72 format!("{}", model.clone().cyan())
73 };
74 println!(" {}{}", model_status, colored_model);
75 }
76 if models.len() > 3 {
77 println!(" {} +{} more models", "...".dimmed(), models.len() - 3);
78 }
79 } else {
80 println!(" {}", "・ Setup required".yellow());
81 }
82
83 let configured = is_provider_configured(&config, provider_name);
85 let config_status = if configured {
86 format!("{}", "✓ Configured".green())
87 } else {
88 format!("{}", "・ Not configured".yellow())
89 };
90 println!(" {}", config_status);
91 println!();
92 }
93
94 println!("{}", "・ Current Config".bold().underline());
96 println!("Provider: {}", config.preferences.default_provider.cyan());
97 println!("Model: {}", config.preferences.default_model.cyan());
98
99 Ok(())
100}
101
102fn is_provider_configured(config: &DotConfig, provider: &str) -> bool {
104 match provider {
105 "openai" => config
106 .providers
107 .openai
108 .as_ref()
109 .map(|p| p.enabled)
110 .unwrap_or(false),
111 "anthropic" => config
112 .providers
113 .anthropic
114 .as_ref()
115 .map(|p| p.enabled)
116 .unwrap_or(false),
117 "gemini" => config
118 .providers
119 .gemini
120 .as_ref()
121 .map(|p| p.enabled)
122 .unwrap_or(false),
123 "openrouter" => config
124 .providers
125 .openrouter
126 .as_ref()
127 .map(|p| p.enabled)
128 .unwrap_or(false),
129 _ => false,
130 }
131}
132
133async fn handle_set_provider(_cli: &Cli, provider: &str) -> Result<()> {
135 let factory = get_factory().lock().unwrap();
136 let available = factory.list_providers();
137
138 if !available.contains(&provider.to_string()) {
139 return Err(anyhow!(
140 "Unknown provider '{}'. Available: {}",
141 provider,
142 available.join(", ")
143 ));
144 }
145
146 let manager = get_dot_manager().lock().unwrap();
147 manager.update_config(|config| {
148 config.preferences.default_provider = provider.to_string();
149 })?;
150
151 println!(
152 "{} Provider set to: {}",
153 "✓".green(),
154 provider.bold().green()
155 );
156 println!(
157 "{} Configure: {}",
158 "・".blue(),
159 format!("vtcode models config {} --api-key YOUR_KEY", provider).dimmed()
160 );
161
162 Ok(())
163}
164
165async fn handle_set_model(_cli: &Cli, model: &str) -> Result<()> {
167 let manager = get_dot_manager().lock().unwrap();
168 manager.update_config(|config| {
169 config.preferences.default_model = model.to_string();
170 })?;
171
172 println!("{} Model set to: {}", "✓".green(), model.bold().green());
173 Ok(())
174}
175
176async fn handle_config_provider(
178 _cli: &Cli,
179 provider: &str,
180 api_key: Option<&str>,
181 base_url: Option<&str>,
182 model: Option<&str>,
183) -> Result<()> {
184 let manager = get_dot_manager().lock().unwrap();
185 let mut config = manager.load_config()?;
186
187 match provider {
188 "openai" | "anthropic" | "gemini" | "openrouter" => {
189 configure_standard_provider(&mut config, provider, api_key, model)?;
190 }
191 _ => return Err(anyhow!("Unsupported provider: {}", provider)),
192 }
193
194 manager.save_config(&config)?;
195 println!("{} {} configured!", "✓".green(), provider.bold().green());
196
197 if let Some(key) = api_key {
198 let masked = mask_api_key(key);
199 println!(" API Key: {}", masked.dimmed());
200 }
201 if let Some(url) = base_url {
202 println!(" Base URL: {}", url.dimmed());
203 }
204 if let Some(m) = model {
205 println!(" Model: {}", m.dimmed());
206 }
207
208 Ok(())
209}
210
211fn configure_standard_provider(
213 config: &mut DotConfig,
214 provider: &str,
215 api_key: Option<&str>,
216 model: Option<&str>,
217) -> Result<()> {
218 let provider_config = match provider {
219 "openai" => config.providers.openai.get_or_insert_with(Default::default),
220 "anthropic" => config
221 .providers
222 .anthropic
223 .get_or_insert_with(Default::default),
224 "gemini" => config.providers.gemini.get_or_insert_with(Default::default),
225 "deepseek" => config
226 .providers
227 .deepseek
228 .get_or_insert_with(Default::default),
229 "openrouter" => config
230 .providers
231 .openrouter
232 .get_or_insert_with(Default::default),
233 "xai" => config.providers.xai.get_or_insert_with(Default::default),
234 _ => return Err(anyhow!("Unknown provider: {}", provider)),
235 };
236
237 if let Some(key) = api_key {
238 provider_config.api_key = Some(key.to_string());
239 }
240 if let Some(m) = model {
241 provider_config.model = Some(m.to_string());
242 }
243 provider_config.enabled = api_key.is_some() || provider_config.api_key.is_some();
244
245 Ok(())
246}
247
248async fn handle_test_provider(_cli: &Cli, provider: &str) -> Result<()> {
250 println!("{} Testing {}...", "・".blue(), provider.bold());
251
252 let config = load_user_config()?;
253 let (api_key, base_url, model) = get_provider_credentials(&config, provider)?;
254
255 let provider_instance =
256 create_provider_with_config(provider, api_key, base_url, model.clone(), None)?;
257
258 let test_request = crate::llm::provider::LLMRequest {
259 messages: vec![crate::llm::provider::Message {
260 role: crate::llm::provider::MessageRole::User,
261 content: "Respond with 'OK' if you receive this message.".to_string(),
262 tool_calls: None,
263 tool_call_id: None,
264 }],
265 system_prompt: None,
266 tools: None,
267 model: model.unwrap_or_else(|| "test".to_string()),
268 max_tokens: Some(10),
269 temperature: Some(0.1),
270 stream: false,
271 tool_choice: None,
272 parallel_tool_calls: None,
273 parallel_tool_config: None,
274 reasoning_effort: None,
275 };
276
277 match provider_instance.generate(test_request).await {
278 Ok(response) => {
279 let content = response.content.unwrap_or_default();
280 if content.to_lowercase().contains("ok") {
281 println!(
282 "{} {} test successful!",
283 "✓".green(),
284 provider.bold().green()
285 );
286 } else {
287 println!(
288 "{} {} responded unexpectedly",
289 "・".yellow(),
290 provider.bold().yellow()
291 );
292 }
293 }
294 Err(e) => {
295 println!("{} {} test failed: {}", "✦".red(), provider.bold().red(), e);
296 }
297 }
298
299 Ok(())
300}
301
302fn get_provider_credentials(
304 config: &DotConfig,
305 provider: &str,
306) -> Result<(Option<String>, Option<String>, Option<String>)> {
307 let get_config = |p: Option<&crate::utils::dot_config::ProviderConfig>| {
308 p.map(|c| (c.api_key.clone(), c.base_url.clone(), c.model.clone()))
309 .unwrap_or((None, None, None))
310 };
311
312 match provider {
313 "openai" => Ok(get_config(config.providers.openai.as_ref())),
314 "anthropic" => Ok(get_config(config.providers.anthropic.as_ref())),
315 "gemini" => Ok(get_config(config.providers.gemini.as_ref())),
316 "deepseek" => Ok(get_config(config.providers.deepseek.as_ref())),
317 "openrouter" => Ok(get_config(config.providers.openrouter.as_ref())),
318 "xai" => Ok(get_config(config.providers.xai.as_ref())),
319 _ => Err(anyhow!("Unknown provider: {}", provider)),
320 }
321}
322
323async fn handle_compare_models(_cli: &Cli) -> Result<()> {
325 println!("{}", "✦ Model Performance Comparison".bold().underline());
326 println!();
327 println!("{} Coming soon! Will compare:", "✦".yellow());
328 println!("• Response times • Token usage • Cost • Quality");
329 println!();
330 println!(
331 "{} Use 'vtcode models list' for available models",
332 "・".blue()
333 );
334
335 Ok(())
336}
337
338async fn handle_model_info(_cli: &Cli, model: &str) -> Result<()> {
340 println!("{} Model Info: {}", "・".blue(), model.bold().underline());
341 println!();
342
343 println!("Model: {}", model.cyan());
344 println!("Provider: {}", infer_provider_from_model(model));
345 println!("Status: {}", "Available".green());
346 println!();
347 println!("{} Check docs/models.json for specs", "・".blue());
348
349 Ok(())
350}
351
352fn infer_provider_from_model(model: &str) -> &'static str {
354 if model.starts_with("gpt-") {
355 "OpenAI"
356 } else if model.starts_with("claude-") {
357 "Anthropic"
358 } else if model.starts_with("gemini-") {
359 "Google Gemini"
360 } else if model.starts_with("grok-") {
361 "xAI"
362 } else if model.starts_with("deepseek-") {
363 "DeepSeek"
364 } else {
365 "Unknown"
366 }
367}
368
369fn mask_api_key(key: &str) -> String {
371 if key.len() > 8 {
372 format!("{}****{}", &key[..4], &key[key.len().saturating_sub(4)..])
373 } else {
374 "****".to_string()
375 }
376}