agentspec_provider/local/
mod.rs1pub mod claude;
2pub mod copilot;
3pub mod gemini;
4pub(crate) mod json;
5
6use crate::error::ProviderError;
7use crate::ir::ProviderConfig;
8use crate::types::AiProvider;
9
10use claude::ClaudeProvider;
11use copilot::CopilotProvider;
12use gemini::GeminiProvider;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
16pub enum LocalBackend {
17 Claude,
18 Copilot,
19 Gemini,
20}
21
22pub async fn resolve_local_provider(config: ProviderConfig) -> anyhow::Result<Box<dyn AiProvider>> {
27 let claude = ClaudeProvider::from_provider_config(&config);
28 let copilot = CopilotProvider::from_provider_config(&config);
29 let gemini = GeminiProvider::from_provider_config(&config);
30
31 let try_fallbacks = |backends: Vec<Box<dyn AiProvider>>| async move {
32 for backend in backends {
33 if backend.is_available().await {
34 return Ok(backend);
35 }
36 }
37 anyhow::bail!(ProviderError::NoProviderAvailable)
38 };
39
40 match config.backend {
41 Some(LocalBackend::Claude) => {
42 if claude.is_available().await {
43 return Ok(Box::new(claude));
44 }
45 eprintln!("warning: claude CLI not found, falling back...");
46 try_fallbacks(vec![Box::new(copilot), Box::new(gemini)]).await
47 }
48 Some(LocalBackend::Copilot) => {
49 if copilot.is_available().await {
50 return Ok(Box::new(copilot));
51 }
52 eprintln!("warning: gh copilot not available, falling back...");
53 try_fallbacks(vec![Box::new(claude), Box::new(gemini)]).await
54 }
55 Some(LocalBackend::Gemini) => {
56 if gemini.is_available().await {
57 return Ok(Box::new(gemini));
58 }
59 eprintln!("warning: gemini CLI not found, falling back...");
60 try_fallbacks(vec![Box::new(claude), Box::new(copilot)]).await
61 }
62 None => try_fallbacks(vec![Box::new(claude), Box::new(copilot), Box::new(gemini)]).await,
63 }
64}