gestura_core/
llm_provider.rs

1//! LLM provider selection and integration.
2//!
3//! Domain types (traits, provider structs) are defined in `gestura-core-llm` and
4//! re-exported here.  This module adds the core-owned [`select_provider`]
5//! function which bridges [`crate::config::AppConfig`] to concrete provider
6//! instances.
7
8pub use gestura_core_llm::*;
9
10use crate::config::AppConfig;
11
12/// Select a provider based on config and context.
13///
14/// If the selected provider is not configured, returns `UnconfiguredProvider` which
15/// will return an error when called. This prevents silent failures.
16pub fn select_provider(config: &AppConfig, _ctx: &AgentContext) -> Box<dyn LlmProvider> {
17    match config.llm.primary.as_str() {
18        "openai" => {
19            if let Some(c) = &config.llm.openai {
20                Box::new(OpenAiProvider {
21                    api_key: c.api_key.clone(),
22                    base_url: c
23                        .base_url
24                        .clone()
25                        .unwrap_or_else(|| "https://api.openai.com".into()),
26                    model: c.model.clone(),
27                })
28            } else {
29                unconfigured_provider("openai")
30            }
31        }
32        "anthropic" => {
33            if let Some(c) = &config.llm.anthropic {
34                Box::new(AnthropicProvider {
35                    api_key: c.api_key.clone(),
36                    base_url: c
37                        .base_url
38                        .clone()
39                        .unwrap_or_else(|| "https://api.anthropic.com".into()),
40                    model: c.model.clone(),
41                    thinking_budget_tokens: c.thinking_budget_tokens,
42                })
43            } else {
44                unconfigured_provider("anthropic")
45            }
46        }
47        "grok" => {
48            if let Some(c) = &config.llm.grok {
49                Box::new(GrokProvider {
50                    api_key: c.api_key.clone(),
51                    base_url: c
52                        .base_url
53                        .clone()
54                        .unwrap_or_else(|| "https://api.x.ai".into()),
55                    model: c.model.clone(),
56                })
57            } else {
58                unconfigured_provider("grok")
59            }
60        }
61        "gemini" => {
62            if let Some(c) = &config.llm.gemini {
63                Box::new(GeminiProvider {
64                    api_key: c.api_key.clone(),
65                    base_url: c
66                        .base_url
67                        .clone()
68                        .unwrap_or_else(|| "https://generativelanguage.googleapis.com".into()),
69                    model: c.model.clone(),
70                })
71            } else {
72                unconfigured_provider("gemini")
73            }
74        }
75        "ollama" => {
76            if let Some(c) = &config.llm.ollama {
77                Box::new(OllamaProvider {
78                    base_url: c.base_url.clone(),
79                    model: c.model.clone(),
80                })
81            } else {
82                unconfigured_provider("ollama")
83            }
84        }
85        other => unconfigured_provider(other),
86    }
87}