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(
27 config: ProviderConfig,
28) -> anyhow::Result<Box<dyn AiProvider>> {
29 let claude = ClaudeProvider::from_provider_config(&config);
30 let copilot = CopilotProvider::from_provider_config(&config);
31 let gemini = GeminiProvider::from_provider_config(&config);
32
33 let try_fallbacks = |backends: Vec<Box<dyn AiProvider>>| async move {
34 for backend in backends {
35 if backend.is_available().await {
36 return Ok(backend);
37 }
38 }
39 anyhow::bail!(ProviderError::NoProviderAvailable)
40 };
41
42 match config.backend {
43 Some(LocalBackend::Claude) => {
44 if claude.is_available().await {
45 return Ok(Box::new(claude));
46 }
47 eprintln!("warning: claude CLI not found, falling back...");
48 try_fallbacks(vec![Box::new(copilot), Box::new(gemini)]).await
49 }
50 Some(LocalBackend::Copilot) => {
51 if copilot.is_available().await {
52 return Ok(Box::new(copilot));
53 }
54 eprintln!("warning: gh copilot not available, falling back...");
55 try_fallbacks(vec![Box::new(claude), Box::new(gemini)]).await
56 }
57 Some(LocalBackend::Gemini) => {
58 if gemini.is_available().await {
59 return Ok(Box::new(gemini));
60 }
61 eprintln!("warning: gemini CLI not found, falling back...");
62 try_fallbacks(vec![Box::new(claude), Box::new(copilot)]).await
63 }
64 None => try_fallbacks(vec![Box::new(claude), Box::new(copilot), Box::new(gemini)]).await,
65 }
66}