use std::future::Future;
use std::path::Path;
use std::pin::Pin;
use std::sync::OnceLock;
use systemprompt_models::services::{
DiscoveryReport, GatewayConfig, ProviderRegistry, ServicesConfig,
};
use crate::config_loader::ConfigLoader;
use crate::error::{ConfigLoadError, ConfigLoadResult};
static SERVICES: OnceLock<ServicesConfig> = OnceLock::new();
pub type DiscoveryFuture<'a> = Pin<Box<dyn Future<Output = DiscoveryReport> + Send + 'a>>;
static DISCOVERY: OnceLock<DiscoveryReport> = OnceLock::new();
#[derive(Debug, Clone, Copy)]
pub struct ServicesBootstrap;
impl ServicesBootstrap {
pub fn init() -> ConfigLoadResult<&'static ServicesConfig> {
if SERVICES.get().is_some() {
return Err(ConfigLoadError::AlreadyInitialized);
}
let services = ConfigLoader::load()?;
Self::install(services)
}
pub fn init_from_path(path: &Path) -> ConfigLoadResult<&'static ServicesConfig> {
if SERVICES.get().is_some() {
return Err(ConfigLoadError::AlreadyInitialized);
}
let services = ConfigLoader::load_from_path(path)?;
Self::install(services)
}
pub async fn try_init_with_discovery<F>(augment: F) -> ConfigLoadResult<&'static ServicesConfig>
where
F: for<'a> FnOnce(&'a mut ProviderRegistry) -> DiscoveryFuture<'a>,
{
if let Some(services) = SERVICES.get() {
return Ok(services);
}
let mut services = ConfigLoader::load()?;
let report = augment(&mut services.providers).await;
services
.validate()
.map_err(|e| ConfigLoadError::Validation(e.to_string()))?;
let installed = Self::install(services)?;
if DISCOVERY.set(report).is_err() {
tracing::warn!(
"catalog discovery report already recorded for this process; keeping the first"
);
}
Ok(installed)
}
#[must_use]
pub fn discovery_report() -> Option<&'static DiscoveryReport> {
DISCOVERY.get()
}
pub fn try_init() -> ConfigLoadResult<&'static ServicesConfig> {
if let Some(services) = SERVICES.get() {
return Ok(services);
}
Self::init()
}
pub fn get() -> ConfigLoadResult<&'static ServicesConfig> {
SERVICES.get().ok_or(ConfigLoadError::NotInitialized)
}
pub fn providers() -> ConfigLoadResult<&'static ProviderRegistry> {
Self::get().map(|s| &s.providers)
}
pub fn gateway() -> ConfigLoadResult<Option<&'static GatewayConfig>> {
Self::get().map(ServicesConfig::gateway_config)
}
#[must_use]
pub fn is_initialized() -> bool {
SERVICES.get().is_some()
}
fn install(services: ServicesConfig) -> ConfigLoadResult<&'static ServicesConfig> {
SERVICES
.set(services)
.map_err(|_already| ConfigLoadError::AlreadyInitialized)?;
SERVICES.get().ok_or(ConfigLoadError::NotInitialized)
}
}