1use crate::cli::{HeaderCommands, ProviderCommands, ProviderPathCommands, ProviderVarsCommands};
4use crate::provider_installer::{AuthType, ProviderInstaller};
5use crate::{chat, config, debug_log};
6use anyhow::Result;
7use colored::Colorize;
8
9pub async fn handle(command: ProviderCommands) -> Result<()> {
11 match command {
12 ProviderCommands::Install { name, force } => {
13 let installer = ProviderInstaller::new()?;
14 installer.install_provider(&name, force).await?;
15 }
16 ProviderCommands::Upgrade { name } => {
17 let installer = ProviderInstaller::new()?;
18 if let Some(provider_name) = name.as_deref() {
19 installer.update_provider(&provider_name).await?;
20 } else {
21 installer.update_all_providers().await?;
22 }
23 }
24 ProviderCommands::Uninstall { name } => {
25 let installer = ProviderInstaller::new()?;
26 installer.uninstall_provider(&name)?;
27 }
28 ProviderCommands::Available { official, tag } => {
29 let installer = ProviderInstaller::new()?;
30 let providers = installer.list_available().await?;
31
32 println!("\n{}", "Available Providers:".bold().blue());
33
34 let mut displayed_count = 0;
35 for (id, metadata) in providers {
36 if official && !metadata.official {
38 continue;
39 }
40 if let Some(ref filter_tag) = tag {
41 if !metadata.tags.contains(filter_tag) {
42 continue;
43 }
44 }
45
46 displayed_count += 1;
47
48 print!(" {} {} - {}", "•".blue(), id.bold(), metadata.name);
49
50 if metadata.official {
51 print!(" {}", "✓ official".green());
52 }
53
54 if !metadata.tags.is_empty() {
55 print!(" [{}]", metadata.tags.join(", ").dimmed());
56 }
57
58 println!("\n {}", metadata.description.dimmed());
59
60 let auth_str = match metadata.auth_type {
62 AuthType::ApiKey => "API Key",
63 AuthType::ServiceAccount => "Service Account",
64 AuthType::OAuth => "OAuth",
65 AuthType::Token => "Token",
66 AuthType::Headers => "Custom Headers",
67 AuthType::None => "None",
68 };
69 println!(" Auth: {}", auth_str.yellow());
70 }
71
72 if displayed_count == 0 {
73 if official {
74 println!("No official providers found.");
75 } else if tag.is_some() {
76 println!("No providers found with the specified tag.");
77 } else {
78 println!("No providers available.");
79 }
80 } else {
81 println!(
82 "\n{} Use 'lc providers install <name>' to install a provider",
83 "💡".yellow()
84 );
85 }
86 }
87 ProviderCommands::Add {
88 name,
89 url,
90 models_path,
91 chat_path,
92 } => {
93 let mut config = config::Config::load()?;
94 config.add_provider_with_paths(name.clone(), url, models_path, chat_path)?;
95 config.save()?;
96 println!("{} Provider '{}' added successfully", "✓".green(), name);
97 }
98 ProviderCommands::Update { name, url } => {
99 let mut config = config::Config::load()?;
100 if !config.has_provider(&name) {
101 anyhow::bail!("Provider '{}' not found", name);
102 }
103 config.add_provider(name.clone(), url)?; config.save()?;
105 println!("{} Provider '{}' updated successfully", "✓".green(), name);
106 }
107 ProviderCommands::Remove { name } => {
108 let mut config = config::Config::load()?;
109 if !config.has_provider(&name) {
110 anyhow::bail!("Provider '{}' not found", name);
111 }
112 config.providers.remove(&name);
113 config.save()?;
114 println!("{} Provider '{}' removed successfully", "✓".green(), name);
115 }
116 ProviderCommands::List => {
117 let config = config::Config::load()?;
118 if config.providers.is_empty() {
119 println!("No providers configured.");
120 return Ok(());
121 }
122
123 println!("\n{}", "Configured Providers:".bold().blue());
124
125 let keys =
127 crate::keys::KeysConfig::load().unwrap_or_else(|_| crate::keys::KeysConfig::new());
128
129 let mut sorted_providers: Vec<_> = config.providers.iter().collect();
131 sorted_providers.sort_by(|a, b| a.0.cmp(b.0));
132
133 for (name, provider_config) in sorted_providers {
134 let has_key = keys.has_auth(name);
136 let key_status = if has_key { "✓".green() } else { "✗".red() };
137 println!(
138 " {} {} - {} (API Key: {})",
139 "•".blue(),
140 name.bold(),
141 provider_config.endpoint,
142 key_status
143 );
144 }
145 }
146 ProviderCommands::Models { name, refresh } => {
147 debug_log!(
148 "Handling provider models command for '{}', refresh: {}",
149 name,
150 refresh
151 );
152
153 let config = config::Config::load()?;
154 let _provider_config = config.get_provider(&name)?;
155
156 debug_log!("Provider '{}' found in config", name);
157
158 match crate::unified_cache::UnifiedCache::fetch_and_cache_provider_models(
160 &name, refresh,
161 )
162 .await
163 {
164 Ok(models) => {
165 debug_log!(
166 "Successfully fetched {} models for provider '{}'",
167 models.len(),
168 name
169 );
170 println!("\n{} Available models:", "Models:".bold());
171 display_provider_models(&models)?;
172 }
173 Err(e) => {
174 debug_log!("Unified cache failed for provider '{}': {}", name, e);
175 eprintln!("Error fetching models from provider '{}': {}", name, e);
176
177 debug_log!(
179 "Attempting fallback to basic client listing for provider '{}'",
180 name
181 );
182 let mut config_mut = config.clone();
183 match chat::create_authenticated_client(&mut config_mut, &name).await {
184 Ok(client) => {
185 debug_log!("Created fallback client for provider '{}'", name);
186 if config_mut.get_cached_token(&name) != config.get_cached_token(&name)
188 {
189 debug_log!("Tokens updated for provider '{}', saving config", name);
190 config_mut.save()?;
191 }
192
193 match client.list_models().await {
194 Ok(models) => {
195 debug_log!(
196 "Fallback client returned {} models for provider '{}'",
197 models.len(),
198 name
199 );
200 println!(
201 "\n{} Available models (basic listing):",
202 "Models:".bold()
203 );
204 for model in models {
205 println!(" • {}", model.id);
206 }
207 }
208 Err(e2) => {
209 debug_log!(
210 "Fallback client failed for provider '{}': {}",
211 name,
212 e2
213 );
214 anyhow::bail!("Failed to fetch models: {}", e2);
215 }
216 }
217 }
218 Err(e2) => {
219 debug_log!(
220 "Failed to create fallback client for provider '{}': {}",
221 name,
222 e2
223 );
224 anyhow::bail!("Failed to create client: {}", e2);
225 }
226 }
227 }
228 }
229 }
230 ProviderCommands::Headers { provider, command } => {
231 let mut config = config::Config::load()?;
232
233 if !config.has_provider(&provider) {
234 anyhow::bail!("Provider '{}' not found", provider);
235 }
236
237 match command {
238 HeaderCommands::Add { name, value } => {
239 config.add_header(provider.clone(), name.clone(), value.clone())?;
240 config.save()?;
241 println!(
242 "{} Header '{}' added to provider '{}'",
243 "✓".green(),
244 name,
245 provider
246 );
247 }
248 HeaderCommands::Delete { name } => {
249 config.remove_header(provider.clone(), name.clone())?;
250 config.save()?;
251 println!(
252 "{} Header '{}' removed from provider '{}'",
253 "✓".green(),
254 name,
255 provider
256 );
257 }
258 HeaderCommands::List => {
259 let headers = config.list_headers(&provider)?;
260 if headers.is_empty() {
261 println!("No custom headers configured for provider '{}'", provider);
262 } else {
263 println!(
264 "\n{} Custom headers for provider '{}':",
265 "Headers:".bold().blue(),
266 provider
267 );
268 for (name, value) in headers {
269 println!(" {} {}: {}", "•".blue(), name.bold(), value);
270 }
271 }
272 }
273 }
274 }
275 ProviderCommands::TokenUrl { provider, url } => {
276 let mut config = config::Config::load()?;
277
278 if !config.has_provider(&provider) {
279 anyhow::bail!("Provider '{}' not found", provider);
280 }
281
282 config.set_token_url(provider.clone(), url.clone())?;
283 config.save()?;
284 println!("{} Token URL set for provider '{}'", "✓".green(), provider);
285 }
286 ProviderCommands::Vars { provider, command } => {
287 let mut config = config::Config::load()?;
288 if !config.has_provider(&provider) {
289 anyhow::bail!("Provider '{}' not found", provider);
290 }
291 match command {
292 ProviderVarsCommands::Set { key, value } => {
293 config.set_provider_var(&provider, &key, &value)?;
294 config.save()?;
295 println!(
296 "{} Set var '{}'='{}' for provider '{}'",
297 "✓".green(),
298 key,
299 value,
300 provider
301 );
302 }
303 ProviderVarsCommands::Get { key } => {
304 match config.get_provider_var(&provider, &key) {
305 Some(val) => println!("{}", val),
306 None => anyhow::bail!("Var '{}' not set for provider '{}'", key, provider),
307 }
308 }
309 ProviderVarsCommands::List => {
310 let vars = config.list_provider_vars(&provider)?;
311 if vars.is_empty() {
312 println!("No vars set for provider '{}'", provider);
313 } else {
314 println!(
315 "\n{} Vars for provider '{}':",
316 "Vars:".bold().blue(),
317 provider
318 );
319 for (k, v) in vars {
320 println!(" {} {} = {}", "•".blue(), k.bold(), v);
321 }
322 }
323 }
324 }
325 }
326 ProviderCommands::Paths { provider, command } => {
327 let mut config = config::Config::load()?;
328 if !config.has_provider(&provider) {
329 anyhow::bail!("Provider '{}' not found", provider);
330 }
331 match command {
332 ProviderPathCommands::Add {
333 models_path,
334 chat_path,
335 images_path,
336 embeddings_path,
337 } => {
338 let mut updated = false;
339 if let Some(path) = models_path.as_deref() {
340 config.set_provider_models_path(&provider, &path)?;
341 println!(
342 "{} Models path set to '{}' for provider '{}'",
343 "✓".green(),
344 path,
345 provider
346 );
347 updated = true;
348 }
349 if let Some(path) = chat_path.as_deref() {
350 config.set_provider_chat_path(&provider, &path)?;
351 println!(
352 "{} Chat path set to '{}' for provider '{}'",
353 "✓".green(),
354 path,
355 provider
356 );
357 updated = true;
358 }
359 if let Some(path) = images_path.as_deref() {
360 config.set_provider_images_path(&provider, &path)?;
361 println!(
362 "{} Images path set to '{}' for provider '{}'",
363 "✓".green(),
364 path,
365 provider
366 );
367 updated = true;
368 }
369 if let Some(path) = embeddings_path.as_deref() {
370 config.set_provider_embeddings_path(&provider, &path)?;
371 println!(
372 "{} Embeddings path set to '{}' for provider '{}'",
373 "✓".green(),
374 path,
375 provider
376 );
377 updated = true;
378 }
379 if !updated {
380 anyhow::bail!("No paths specified. Use -m, -c, -i, or -e to set paths.");
381 }
382 config.save()?;
383 }
384 ProviderPathCommands::Delete {
385 models,
386 chat,
387 images,
388 embeddings,
389 } => {
390 let mut updated = false;
391 if models {
392 config.reset_provider_models_path(&provider)?;
393 println!(
394 "{} Models path reset to default for provider '{}'",
395 "✓".green(),
396 provider
397 );
398 updated = true;
399 }
400 if chat {
401 config.reset_provider_chat_path(&provider)?;
402 println!(
403 "{} Chat path reset to default for provider '{}'",
404 "✓".green(),
405 provider
406 );
407 updated = true;
408 }
409 if images {
410 config.reset_provider_images_path(&provider)?;
411 println!(
412 "{} Images path reset to default for provider '{}'",
413 "✓".green(),
414 provider
415 );
416 updated = true;
417 }
418 if embeddings {
419 config.reset_provider_embeddings_path(&provider)?;
420 println!(
421 "{} Embeddings path reset to default for provider '{}'",
422 "✓".green(),
423 provider
424 );
425 updated = true;
426 }
427 if !updated {
428 anyhow::bail!("No paths specified for deletion. Use -m, -c, -i, or -e to delete paths.");
429 }
430 config.save()?;
431 }
432 ProviderPathCommands::List => {
433 let paths = config.list_provider_paths(&provider)?;
434 println!(
435 "\n{} API paths for provider '{}':",
436 "Paths:".bold().blue(),
437 provider
438 );
439 println!(" {} Models: {}", "•".blue(), paths.models_path.bold());
440 println!(" {} Chat: {}", "•".blue(), paths.chat_path.bold());
441 if let Some(ref images_path) = paths.images_path {
442 println!(" {} Images: {}", "•".blue(), images_path.bold());
443 } else {
444 println!(" {} Images: {}", "•".blue(), "not set".dimmed());
445 }
446 if let Some(ref embeddings_path) = paths.embeddings_path {
447 println!(" {} Embeddings: {}", "•".blue(), embeddings_path.bold());
448 } else {
449 println!(" {} Embeddings: {}", "•".blue(), "not set".dimmed());
450 }
451 }
452 }
453 }
454 }
455 Ok(())
456}
457
458fn display_provider_models(models: &[crate::model_metadata::ModelMetadata]) -> Result<()> {
460 use colored::Colorize;
461
462 for model in models {
463 if !model.supports_tools
465 && !model.supports_function_calling
466 && !model.supports_vision
467 && !model.supports_audio
468 && !model.supports_reasoning
469 && !model.supports_code
470 {
471 debug_log!("All capability flags are false for model '{}' - this might indicate a defaulting bug", model.id);
472 }
473
474 let mut capabilities = Vec::new();
476 if model.supports_tools || model.supports_function_calling {
477 capabilities.push("🔧 tools".blue());
478 }
479 if model.supports_vision {
480 capabilities.push("👁 vision".magenta());
481 }
482 if model.supports_audio {
483 capabilities.push("🔊 audio".yellow());
484 }
485 if model.supports_reasoning {
486 capabilities.push("🧠 reasoning".cyan());
487 }
488 if model.supports_code {
489 capabilities.push("💻 code".green());
490 }
491
492 let mut info_parts = Vec::new();
494 if let Some(ctx) = model.context_length {
495 if ctx >= 1000000 {
496 info_parts.push(format!("{}m ctx", ctx / 1000000));
497 } else if ctx >= 1000 {
498 info_parts.push(format!("{}k ctx", ctx / 1000));
499 } else {
500 info_parts.push(format!("{} ctx", ctx));
501 }
502 }
503 if let Some(max_out) = model.max_output_tokens {
504 if max_out >= 1000 {
505 info_parts.push(format!("{}k out", max_out / 1000));
506 } else {
507 info_parts.push(format!("{} out", max_out));
508 }
509 }
510 if let Some(input_price) = model.input_price_per_m {
511 info_parts.push(format!("${:.2}/M in", input_price));
512 }
513 if let Some(output_price) = model.output_price_per_m {
514 info_parts.push(format!("${:.2}/M out", output_price));
515 }
516
517 let model_display = if let Some(ref display_name) = model.display_name {
519 if display_name != &model.id {
520 format!("{} ({})", model.id, display_name)
521 } else {
522 model.id.clone()
523 }
524 } else {
525 model.id.clone()
526 };
527
528 print!(" {} {}", "•".blue(), model_display.bold());
529
530 if !capabilities.is_empty() {
531 let capability_strings: Vec<String> =
532 capabilities.iter().map(|c| c.to_string()).collect();
533 print!(" [{}]", capability_strings.join(" "));
534 }
535
536 if !info_parts.is_empty() {
537 print!(" ({})", info_parts.join(", ").dimmed());
538 }
539
540 println!();
541 }
542
543 Ok(())
544}