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(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}