mod boundary;
mod cache;
mod fetch;
mod types;
pub use cache::{load_api_catalog, CacheError, CacheWarning};
pub use fetch::{CatalogHttpClient, RealCatalogFetcher};
pub use types::{ApiCatalog, Model, Provider};
use std::sync::Arc;
pub const API_URL: &str = "https://models.dev/api.json";
pub const DEFAULT_CACHE_TTL_SECONDS: u64 = 24 * 60 * 60;
pub const CACHE_TTL_ENV_VAR: &str = "RALPH_OPENCODE_CACHE_TTL_SECONDS";
pub trait CatalogLoader: Send + Sync {
fn load(&self) -> Result<ApiCatalog, CacheError>;
}
#[derive(Clone)]
pub struct RealCatalogLoader {
fetcher: Arc<dyn fetch::CatalogHttpClient>,
}
impl RealCatalogLoader {
pub fn new(fetcher: Arc<dyn fetch::CatalogHttpClient>) -> Self {
Self { fetcher }
}
pub fn with_fetcher<F>(fetcher: F) -> Self
where
F: fetch::CatalogHttpClient + 'static,
{
Self::new(Arc::new(fetcher))
}
}
impl CatalogLoader for RealCatalogLoader {
fn load(&self) -> Result<ApiCatalog, CacheError> {
let (catalog, warnings) = cache::load_api_catalog(self.fetcher.as_ref())?;
warnings.into_iter().for_each(|warning| {
match warning {
CacheWarning::StaleCacheUsed { stale_days, error } => {
eprintln!("Warning: Failed to fetch fresh OpenCode API catalog ({error}), using stale cache from {stale_days} days ago");
}
CacheWarning::CacheSaveFailed { error } => {
eprintln!("Warning: Failed to cache OpenCode API catalog: {error}");
}
}
});
Ok(catalog)
}
}