use crate::utils::error::OpenCratesError;
use crate::utils::openai_agents::Usage;
use crate::utils::templates::CrateSpec;
use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerationRequest {
pub spec: CrateSpec,
#[serde(default)]
pub prompt: Option<String>,
#[serde(default)]
pub max_tokens: Option<u32>,
#[serde(default)]
pub model: Option<String>,
#[serde(default)]
pub temperature: Option<f32>,
#[serde(default)]
pub context: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerationResponse {
pub preview: String,
pub finish_reason: Option<String>,
pub metrics: Usage,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SearchResult {
pub title: String,
pub description: String,
pub url: String,
pub relevance_score: f32,
}
#[derive(Debug, Clone)]
pub struct SearchOptions {
pub limit: usize,
pub max_results: usize,
pub timeout: Option<Duration>,
pub include_code: bool,
pub include_snippets: bool,
pub language_filter: Option<String>,
pub filter_duplicates: bool,
}
impl Default for SearchOptions {
fn default() -> Self {
Self {
limit: 10,
max_results: 10,
timeout: Some(Duration::from_secs(30)),
include_code: false,
include_snippets: true,
language_filter: None,
filter_duplicates: true,
}
}
}
#[async_trait]
pub trait SearchProvider: Send + Sync {
async fn search(
&self,
query: &str,
options: &SearchOptions,
) -> Result<Vec<SearchResult>, OpenCratesError>;
async fn search_code(
&self,
query: &str,
language: Option<&str>,
) -> Result<Vec<SearchResult>, OpenCratesError>;
async fn get_documentation(&self, crate_name: &str) -> Result<String, OpenCratesError>;
async fn health_check(&self) -> Result<bool, OpenCratesError>;
fn name(&self) -> &str;
}
#[async_trait]
pub trait LLMProvider: Send + Sync {
async fn generate(
&self,
request: &GenerationRequest,
) -> Result<GenerationResponse, OpenCratesError>;
async fn health_check(&self) -> Result<bool, OpenCratesError>;
fn name(&self) -> &str;
fn as_any(&self) -> &dyn std::any::Any;
}
pub struct ProviderRegistry {
providers: std::collections::HashMap<String, Box<dyn LLMProvider>>,
}
impl Default for ProviderRegistry {
fn default() -> Self {
Self::new()
}
}
impl ProviderRegistry {
#[must_use]
pub fn new() -> Self {
Self {
providers: std::collections::HashMap::new(),
}
}
pub fn register(&mut self, name: &str, provider: Box<dyn LLMProvider>) -> Result<()> {
if self.providers.contains_key(name) {
return Err(anyhow::anyhow!(
"Provider with name '{}' already registered",
name
));
}
self.providers.insert(name.to_string(), provider);
Ok(())
}
#[must_use]
pub fn get_provider(&self, name: &str) -> Option<&dyn LLMProvider> {
self.providers.get(name).map(std::convert::AsRef::as_ref)
}
}
pub struct ProviderSelector;
impl ProviderSelector {
pub fn select<'a>(registry: &'a ProviderRegistry, name: &str) -> Result<&'a dyn LLMProvider> {
registry
.get_provider(name)
.ok_or_else(|| anyhow::anyhow!("Provider '{}' not found", name))
}
}
#[async_trait]
pub trait LegacyLLMProvider: Send + Sync + Clone {
async fn generate(
&self,
request: GenerationRequest,
) -> Result<GenerationResponse, OpenCratesError>;
async fn set_model(&self, model: &str) -> Result<(), OpenCratesError>;
async fn validate_api_key(&self) -> Result<bool, OpenCratesError>;
}
pub use aichat::AIChatIntegration;
pub use aider::AiderIntegration;
pub use codex::CodexProvider;
pub use openai::OpenAIProvider;
pub use websearch::WebSearchProvider;
pub mod aichat;
pub mod aider;
pub mod codex;
pub mod enhanced_openai;
pub mod model_types;
pub mod openai;
pub mod websearch;