use async_trait::async_trait; use crate::client::{GenerateRequest, GenerateResponse, EmbedRequest, EmbedResponse}; #[async_trait] pub trait ProviderAdapter: Send + Sync { /// Name for routing (ollama, openrouter, etc.) fn name(&self) -> &str; /// Chat completion — returns text, model, token counts async fn chat(&self, req: GenerateRequest) -> Result; /// Embeddings — returns vectors, model, dimensions async fn embed(&self, req: EmbedRequest) -> Result; /// Unload model from VRAM (optional, no-op if not supported) async fn unload(&self, _model: &str) -> Result<(), String> { Ok(()) } /// Health check async fn health(&self) -> Result; } /// Routing key extracted from model name. /// - "qwen3.5:latest" → "ollama" /// - "openrouter/anthropic/claude-3.5-sonnet" → "openrouter" /// - "gpt-4o" → "ollama" (default) pub fn provider_key(model: &str) -> &'static str { let lower = model.to_lowercase(); if lower.starts_with("openrouter/") { "openrouter" } else if lower.starts_with("gemini") { "gemini" } else if lower.starts_with("claude") { "claude" } else { "ollama" // default: local Ollama } }