use super::args::{Cli, ModelCommands};
use crate::llm::factory::{create_provider_with_config, get_factory};
use crate::utils::dot_config::{DotConfig, get_dot_manager, load_user_config};
use anyhow::{Result, anyhow};
use colored::*;
pub async fn handle_models_command(cli: &Cli, command: &ModelCommands) -> Result<()> {
match command {
ModelCommands::List => handle_list_models(cli).await,
ModelCommands::SetProvider { provider } => handle_set_provider(cli, provider).await,
ModelCommands::SetModel { model } => handle_set_model(cli, model).await,
ModelCommands::Config {
provider,
api_key,
base_url,
model,
} => {
handle_config_provider(
cli,
provider,
api_key.as_deref(),
base_url.as_deref(),
model.as_deref(),
)
.await
}
ModelCommands::Test { provider } => handle_test_provider(cli, provider).await,
ModelCommands::Compare => handle_compare_models(cli).await,
ModelCommands::Info { model } => handle_model_info(cli, model).await,
}
}
async fn handle_list_models(_cli: &Cli) -> Result<()> {
println!("{}", "Available Providers & Models".bold().underline());
println!();
let factory = get_factory().lock().unwrap();
let config = load_user_config().unwrap_or_default();
let providers = factory.list_providers();
for provider_name in &providers {
let is_current = config.preferences.default_provider == *provider_name;
let status = if is_current { "✦" } else { " " };
let provider_display = format!("{}{}", status, provider_name.to_uppercase());
let colored_provider = if is_current {
format!("{}", provider_display.bold().green())
} else {
format!("{}", provider_display.bold())
};
println!("{}", colored_provider);
if let Ok(provider) =
create_provider_with_config(provider_name, Some("dummy".to_string()), None, None, None)
{
let models = provider.supported_models();
let current_model = &config.preferences.default_model;
for model in models.iter().take(3) {
let is_current_model = current_model == model;
let model_status = if is_current_model { "⭐" } else { " " };
let colored_model = if is_current_model {
format!("{}", model.clone().bold().cyan())
} else {
format!("{}", model.clone().cyan())
};
println!(" {}{}", model_status, colored_model);
}
if models.len() > 3 {
println!(" {} +{} more models", "...".dimmed(), models.len() - 3);
}
} else {
println!(" {}", "・ Setup required".yellow());
}
let configured = is_provider_configured(&config, provider_name);
let config_status = if configured {
format!("{}", "✓ Configured".green())
} else {
format!("{}", "・ Not configured".yellow())
};
println!(" {}", config_status);
println!();
}
println!("{}", "・ Current Config".bold().underline());
println!("Provider: {}", config.preferences.default_provider.cyan());
println!("Model: {}", config.preferences.default_model.cyan());
Ok(())
}
fn is_provider_configured(config: &DotConfig, provider: &str) -> bool {
match provider {
"openai" => config
.providers
.openai
.as_ref()
.map(|p| p.enabled)
.unwrap_or(false),
"anthropic" => config
.providers
.anthropic
.as_ref()
.map(|p| p.enabled)
.unwrap_or(false),
"gemini" => config
.providers
.gemini
.as_ref()
.map(|p| p.enabled)
.unwrap_or(false),
"openrouter" => config
.providers
.openrouter
.as_ref()
.map(|p| p.enabled)
.unwrap_or(false),
_ => false,
}
}
async fn handle_set_provider(_cli: &Cli, provider: &str) -> Result<()> {
let factory = get_factory().lock().unwrap();
let available = factory.list_providers();
if !available.contains(&provider.to_string()) {
return Err(anyhow!(
"Unknown provider '{}'. Available: {}",
provider,
available.join(", ")
));
}
let manager = get_dot_manager().lock().unwrap();
manager.update_config(|config| {
config.preferences.default_provider = provider.to_string();
})?;
println!(
"{} Provider set to: {}",
"✓".green(),
provider.bold().green()
);
println!(
"{} Configure: {}",
"・".blue(),
format!("vtcode models config {} --api-key YOUR_KEY", provider).dimmed()
);
Ok(())
}
async fn handle_set_model(_cli: &Cli, model: &str) -> Result<()> {
let manager = get_dot_manager().lock().unwrap();
manager.update_config(|config| {
config.preferences.default_model = model.to_string();
})?;
println!("{} Model set to: {}", "✓".green(), model.bold().green());
Ok(())
}
async fn handle_config_provider(
_cli: &Cli,
provider: &str,
api_key: Option<&str>,
base_url: Option<&str>,
model: Option<&str>,
) -> Result<()> {
let manager = get_dot_manager().lock().unwrap();
let mut config = manager.load_config()?;
match provider {
"openai" | "anthropic" | "gemini" | "openrouter" => {
configure_standard_provider(&mut config, provider, api_key, model)?;
}
_ => return Err(anyhow!("Unsupported provider: {}", provider)),
}
manager.save_config(&config)?;
println!("{} {} configured!", "✓".green(), provider.bold().green());
if let Some(key) = api_key {
let masked = mask_api_key(key);
println!(" API Key: {}", masked.dimmed());
}
if let Some(url) = base_url {
println!(" Base URL: {}", url.dimmed());
}
if let Some(m) = model {
println!(" Model: {}", m.dimmed());
}
Ok(())
}
fn configure_standard_provider(
config: &mut DotConfig,
provider: &str,
api_key: Option<&str>,
model: Option<&str>,
) -> Result<()> {
let provider_config = match provider {
"openai" => config.providers.openai.get_or_insert_with(Default::default),
"anthropic" => config
.providers
.anthropic
.get_or_insert_with(Default::default),
"gemini" => config.providers.gemini.get_or_insert_with(Default::default),
"deepseek" => config
.providers
.deepseek
.get_or_insert_with(Default::default),
"openrouter" => config
.providers
.openrouter
.get_or_insert_with(Default::default),
"xai" => config.providers.xai.get_or_insert_with(Default::default),
_ => return Err(anyhow!("Unknown provider: {}", provider)),
};
if let Some(key) = api_key {
provider_config.api_key = Some(key.to_string());
}
if let Some(m) = model {
provider_config.model = Some(m.to_string());
}
provider_config.enabled = api_key.is_some() || provider_config.api_key.is_some();
Ok(())
}
async fn handle_test_provider(_cli: &Cli, provider: &str) -> Result<()> {
println!("{} Testing {}...", "・".blue(), provider.bold());
let config = load_user_config()?;
let (api_key, base_url, model) = get_provider_credentials(&config, provider)?;
let provider_instance =
create_provider_with_config(provider, api_key, base_url, model.clone(), None)?;
let test_request = crate::llm::provider::LLMRequest {
messages: vec![crate::llm::provider::Message {
role: crate::llm::provider::MessageRole::User,
content: "Respond with 'OK' if you receive this message.".to_string(),
tool_calls: None,
tool_call_id: None,
}],
system_prompt: None,
tools: None,
model: model.unwrap_or_else(|| "test".to_string()),
max_tokens: Some(10),
temperature: Some(0.1),
stream: false,
tool_choice: None,
parallel_tool_calls: None,
parallel_tool_config: None,
reasoning_effort: None,
};
match provider_instance.generate(test_request).await {
Ok(response) => {
let content = response.content.unwrap_or_default();
if content.to_lowercase().contains("ok") {
println!(
"{} {} test successful!",
"✓".green(),
provider.bold().green()
);
} else {
println!(
"{} {} responded unexpectedly",
"・".yellow(),
provider.bold().yellow()
);
}
}
Err(e) => {
println!("{} {} test failed: {}", "✦".red(), provider.bold().red(), e);
}
}
Ok(())
}
fn get_provider_credentials(
config: &DotConfig,
provider: &str,
) -> Result<(Option<String>, Option<String>, Option<String>)> {
let get_config = |p: Option<&crate::utils::dot_config::ProviderConfig>| {
p.map(|c| (c.api_key.clone(), c.base_url.clone(), c.model.clone()))
.unwrap_or((None, None, None))
};
match provider {
"openai" => Ok(get_config(config.providers.openai.as_ref())),
"anthropic" => Ok(get_config(config.providers.anthropic.as_ref())),
"gemini" => Ok(get_config(config.providers.gemini.as_ref())),
"deepseek" => Ok(get_config(config.providers.deepseek.as_ref())),
"openrouter" => Ok(get_config(config.providers.openrouter.as_ref())),
"xai" => Ok(get_config(config.providers.xai.as_ref())),
_ => Err(anyhow!("Unknown provider: {}", provider)),
}
}
async fn handle_compare_models(_cli: &Cli) -> Result<()> {
println!("{}", "✦ Model Performance Comparison".bold().underline());
println!();
println!("{} Coming soon! Will compare:", "✦".yellow());
println!("• Response times • Token usage • Cost • Quality");
println!();
println!(
"{} Use 'vtcode models list' for available models",
"・".blue()
);
Ok(())
}
async fn handle_model_info(_cli: &Cli, model: &str) -> Result<()> {
println!("{} Model Info: {}", "・".blue(), model.bold().underline());
println!();
println!("Model: {}", model.cyan());
println!("Provider: {}", infer_provider_from_model(model));
println!("Status: {}", "Available".green());
println!();
println!("{} Check docs/models.json for specs", "・".blue());
Ok(())
}
fn infer_provider_from_model(model: &str) -> &'static str {
if model.starts_with("gpt-") {
"OpenAI"
} else if model.starts_with("claude-") {
"Anthropic"
} else if model.starts_with("gemini-") {
"Google Gemini"
} else if model.starts_with("grok-") {
"xAI"
} else if model.starts_with("deepseek-") {
"DeepSeek"
} else {
"Unknown"
}
}
fn mask_api_key(key: &str) -> String {
if key.len() > 8 {
format!("{}****{}", &key[..4], &key[key.len().saturating_sub(4)..])
} else {
"****".to_string()
}
}