systemprompt_loader/
services_bootstrap.rs1use std::future::Future;
26use std::path::Path;
27use std::pin::Pin;
28use std::sync::OnceLock;
29
30use systemprompt_models::services::{
31 DiscoveryReport, GatewayConfig, ProviderRegistry, ServicesConfig,
32};
33
34use crate::config_loader::ConfigLoader;
35use crate::error::{ConfigLoadError, ConfigLoadResult};
36
37static SERVICES: OnceLock<ServicesConfig> = OnceLock::new();
38pub type DiscoveryFuture<'a> = Pin<Box<dyn Future<Output = DiscoveryReport> + Send + 'a>>;
39
40static DISCOVERY: OnceLock<DiscoveryReport> = OnceLock::new();
41
42#[derive(Debug, Clone, Copy)]
43pub struct ServicesBootstrap;
44
45impl ServicesBootstrap {
46 pub fn init() -> ConfigLoadResult<&'static ServicesConfig> {
47 if SERVICES.get().is_some() {
48 return Err(ConfigLoadError::AlreadyInitialized);
49 }
50 let services = ConfigLoader::load()?;
51 Self::install(services)
52 }
53
54 pub fn init_from_path(path: &Path) -> ConfigLoadResult<&'static ServicesConfig> {
55 if SERVICES.get().is_some() {
56 return Err(ConfigLoadError::AlreadyInitialized);
57 }
58 let services = ConfigLoader::load_from_path(path)?;
59 Self::install(services)
60 }
61
62 pub async fn try_init_with_discovery<F>(augment: F) -> ConfigLoadResult<&'static ServicesConfig>
66 where
67 F: for<'a> FnOnce(&'a mut ProviderRegistry) -> DiscoveryFuture<'a>,
68 {
69 if let Some(services) = SERVICES.get() {
70 return Ok(services);
71 }
72 let mut services = ConfigLoader::load()?;
73 let report = augment(&mut services.providers).await;
74 services
75 .validate()
76 .map_err(|e| ConfigLoadError::Validation(e.to_string()))?;
77 let installed = Self::install(services)?;
78 if DISCOVERY.set(report).is_err() {
79 tracing::warn!(
80 "catalog discovery report already recorded for this process; keeping the first"
81 );
82 }
83 Ok(installed)
84 }
85
86 #[must_use]
87 pub fn discovery_report() -> Option<&'static DiscoveryReport> {
88 DISCOVERY.get()
89 }
90
91 pub fn try_init() -> ConfigLoadResult<&'static ServicesConfig> {
92 if let Some(services) = SERVICES.get() {
93 return Ok(services);
94 }
95 Self::init()
96 }
97
98 pub fn get() -> ConfigLoadResult<&'static ServicesConfig> {
99 SERVICES.get().ok_or(ConfigLoadError::NotInitialized)
100 }
101
102 pub fn providers() -> ConfigLoadResult<&'static ProviderRegistry> {
103 Self::get().map(|s| &s.providers)
104 }
105
106 pub fn gateway() -> ConfigLoadResult<Option<&'static GatewayConfig>> {
107 Self::get().map(ServicesConfig::gateway_config)
108 }
109
110 #[must_use]
111 pub fn is_initialized() -> bool {
112 SERVICES.get().is_some()
113 }
114
115 fn install(services: ServicesConfig) -> ConfigLoadResult<&'static ServicesConfig> {
116 SERVICES
117 .set(services)
118 .map_err(|_already| ConfigLoadError::AlreadyInitialized)?;
119 SERVICES.get().ok_or(ConfigLoadError::NotInitialized)
120 }
121}