systemprompt_loader/
services_state.rs1use std::future::Future;
24use std::path::Path;
25use std::pin::Pin;
26use std::sync::Arc;
27
28use systemprompt_manifest::services::catalog::{CatalogContext, CatalogDocument, ResolvedCatalog};
29use systemprompt_manifest::services::{DiscoveryReport, ServicesConfig, ServicesView};
30use systemprompt_traits::ConfigWatch;
31
32use systemprompt_manifest::profile::Profile;
33
34use crate::boot::BootContext;
35use crate::config_loader::ConfigLoader;
36use crate::error::ConfigLoadResult;
37
38pub type DiscoveryFuture<'a> = Pin<Box<dyn Future<Output = DiscoveryReport> + Send + 'a>>;
39
40#[derive(Debug, Clone)]
41pub struct ServicesState {
42 watch: ConfigWatch<ServicesConfig>,
43 catalog: Arc<ResolvedCatalog>,
44 discovery: Option<Arc<DiscoveryReport>>,
45}
46
47impl ServicesState {
48 pub fn load(boot: &BootContext) -> ConfigLoadResult<Self> {
49 Self::from_config(boot.profile(), boot.load_services_config()?)
50 }
51
52 pub fn load_from_path(path: &Path, profile: &Profile) -> ConfigLoadResult<Self> {
53 Self::from_config(profile, ConfigLoader::load_from_path(path, Some(profile))?)
54 }
55
56 pub async fn load_with_discovery<F>(boot: &BootContext, augment: F) -> ConfigLoadResult<Self>
60 where
61 F: for<'a> FnOnce(&'a mut CatalogDocument) -> DiscoveryFuture<'a>,
62 {
63 let mut services = boot.load_services_config()?;
64 let mut document = take_catalog(&mut services);
65 let report = augment(&mut document).await;
66 restore_catalog(&mut services, document);
67 services.validate()?;
68 let mut state = Self::from_config(boot.profile(), services)?;
69 state.discovery = Some(Arc::new(report));
70 Ok(state)
71 }
72
73 pub fn from_config(profile: &Profile, services: ServicesConfig) -> ConfigLoadResult<Self> {
74 let catalog = services.resolve_catalog(&catalog_context(profile))?;
75 Ok(Self {
76 watch: ConfigWatch::new(services),
77 catalog: Arc::new(catalog),
78 discovery: None,
79 })
80 }
81
82 #[must_use]
83 pub fn config(&self) -> Arc<ServicesConfig> {
84 self.watch.get()
85 }
86
87 #[must_use]
88 pub const fn watch(&self) -> &ConfigWatch<ServicesConfig> {
89 &self.watch
90 }
91
92 #[must_use]
93 pub fn view(&self) -> ServicesView {
94 ServicesView {
95 config: self.watch.clone(),
96 catalog: Arc::clone(&self.catalog),
97 }
98 }
99
100 #[must_use]
101 pub const fn catalog(&self) -> &Arc<ResolvedCatalog> {
102 &self.catalog
103 }
104
105 #[must_use]
106 pub fn discovery_report(&self) -> Option<&DiscoveryReport> {
107 self.discovery.as_deref()
108 }
109}
110
111#[must_use]
112pub fn catalog_context(profile: &Profile) -> CatalogContext {
113 CatalogContext::for_install(profile.site.name.clone())
114}
115
116fn take_catalog(services: &mut ServicesConfig) -> CatalogDocument {
117 CatalogDocument {
118 credentials: std::mem::take(&mut services.credentials),
119 providers: std::mem::take(&mut services.providers),
120 models: std::mem::take(&mut services.models),
121 deployments: std::mem::take(&mut services.deployments),
122 gateway: None,
123 }
124}
125
126fn restore_catalog(services: &mut ServicesConfig, document: CatalogDocument) {
127 services.credentials = document.credentials;
128 services.providers = document.providers;
129 services.models = document.models;
130 services.deployments = document.deployments;
131}