Skip to main content

agentspec_provider/local/
mod.rs

1pub 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/// Available local AI backends.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
16pub enum LocalBackend {
17    Claude,
18    Copilot,
19    Gemini,
20}
21
22/// Resolve an available local AI provider, with fallback.
23///
24/// If a preferred backend is specified but unavailable, falls back to others.
25/// If no preference, tries claude -> copilot -> gemini.
26pub 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}