Fix: banner on launch, config file loading, 7 providers match setup wizard

BUGFIXES:
- Banner now displays on 'gyandoot run' via gyandoot_tui::banner::print_banner()
- Config now loads from ~/.config/gyandoot/config.toml with fallback chain
  (XDG -> ~/.gyandoot/config.yaml -> hardcoded defaults), fixing 401 errors
- gyandoot init now writes visible placeholder api_key for users to edit

NEW PROVIDERS (4 added, now 7 total matching setup wizard):
- Google Gemini: Native API with x-goog-api-key auth, streamingGenerateContent
- DeepSeek: OpenAI-compatible wrapper at api.deepseek.com/v1
- OpenRouter: With HTTP-Referer/X-Title headers, model routing
- Groq: OpenAI-compatible wrapper at api.groq.com/openai/v1

All compile with 0 errors.
This commit is contained in:
2026-06-11 21:40:34 +05:30
parent dd21654555
commit fb01b2595a
9 changed files with 1202 additions and 13 deletions

View File

@@ -14,12 +14,20 @@ pub mod openai;
pub mod anthropic;
pub mod ollama;
pub mod streaming;
pub mod gemini;
pub mod deepseek;
pub mod openrouter;
pub mod groq;
// ─── Re-exports ───────────────────────────────────────────────────────────
pub use openai::OpenAIProvider;
pub use anthropic::AnthropicProvider;
pub use ollama::OllamaProvider;
pub use gemini::GeminiProvider;
pub use deepseek::DeepSeekProvider;
pub use openrouter::OpenRouterProvider;
pub use groq::GroqProvider;
pub use streaming::{SseParser, StreamEvent, TokenStream, collect_stream};
// ─── ProviderResponse ─────────────────────────────────────────────────────
@@ -102,6 +110,7 @@ pub enum ProviderKind {
Gemini,
DeepSeek,
OpenRouter,
Groq,
}
impl ProviderKind {
@@ -114,6 +123,7 @@ impl ProviderKind {
"gemini" => Some(ProviderKind::Gemini),
"deepseek" => Some(ProviderKind::DeepSeek),
"openrouter" => Some(ProviderKind::OpenRouter),
"groq" => Some(ProviderKind::Groq),
_ => None,
}
}
@@ -127,6 +137,7 @@ impl ProviderKind {
ProviderKind::Gemini => "gemini",
ProviderKind::DeepSeek => "deepseek",
ProviderKind::OpenRouter => "openrouter",
ProviderKind::Groq => "groq",
}
}
}
@@ -160,7 +171,7 @@ impl ProviderFactory {
config: &gyandoot_core::ProviderRootConfig,
) -> ProviderResult<Box<dyn Provider>> {
match kind {
ProviderKind::OpenAI | ProviderKind::DeepSeek | ProviderKind::OpenRouter => {
ProviderKind::OpenAI => {
let cfg = config.openai.as_ref().ok_or_else(|| gyandoot_core::ProviderError::Api {
status: 0,
message: "OpenAI config not found".to_string(),
@@ -196,8 +207,40 @@ impl ProviderFactory {
status: 0,
message: "Gemini config not found (use openai section)".to_string(),
})?;
// Gemini uses the OpenAI-compatible endpoint
Ok(Box::new(openai::OpenAIProvider::new(
Ok(Box::new(gemini::GeminiProvider::new(
cfg.api_key.clone(),
cfg.model.clone(),
cfg.base_url.clone(),
)))
}
ProviderKind::DeepSeek => {
let cfg = config.openai.as_ref().ok_or_else(|| gyandoot_core::ProviderError::Api {
status: 0,
message: "DeepSeek config not found (use openai section)".to_string(),
})?;
Ok(Box::new(deepseek::DeepSeekProvider::new(
cfg.api_key.clone(),
cfg.model.clone(),
cfg.base_url.clone(),
)))
}
ProviderKind::OpenRouter => {
let cfg = config.openai.as_ref().ok_or_else(|| gyandoot_core::ProviderError::Api {
status: 0,
message: "OpenRouter config not found (use openai section)".to_string(),
})?;
Ok(Box::new(openrouter::OpenRouterProvider::new(
cfg.api_key.clone(),
cfg.model.clone(),
cfg.base_url.clone(),
)))
}
ProviderKind::Groq => {
let cfg = config.openai.as_ref().ok_or_else(|| gyandoot_core::ProviderError::Api {
status: 0,
message: "Groq config not found (use openai section)".to_string(),
})?;
Ok(Box::new(groq::GroqProvider::new(
cfg.api_key.clone(),
cfg.model.clone(),
cfg.base_url.clone(),
@@ -220,7 +263,7 @@ impl ProviderFactory {
let config = gyandoot_core::ProviderRootConfig {
default: name.to_string(),
openai: if matches!(kind, ProviderKind::OpenAI | ProviderKind::DeepSeek | ProviderKind::OpenRouter | ProviderKind::Gemini) {
openai: if matches!(kind, ProviderKind::OpenAI | ProviderKind::DeepSeek | ProviderKind::OpenRouter | ProviderKind::Gemini | ProviderKind::Groq) {
Some(gyandoot_core::OpenAIConfig {
api_key: api_key.to_string(),
model: model.to_string(),
@@ -263,6 +306,7 @@ fn default_base_url(kind: &ProviderKind) -> String {
ProviderKind::Gemini => "https://generativelanguage.googleapis.com/v1".to_string(),
ProviderKind::DeepSeek => "https://api.deepseek.com/v1".to_string(),
ProviderKind::OpenRouter => "https://openrouter.ai/api/v1".to_string(),
ProviderKind::Groq => "https://api.groq.com/openai/v1".to_string(),
}
}
@@ -281,6 +325,7 @@ mod tests {
ProviderKind::Gemini,
ProviderKind::DeepSeek,
ProviderKind::OpenRouter,
ProviderKind::Groq,
] {
let s = kind.to_string();
let parsed = ProviderKind::from_str(&s).unwrap();