systemprompt_loader/
boot.rs1use std::path::Path;
16use std::sync::{Arc, OnceLock};
17
18use systemprompt_config::{ConfigError, ConfigResult, LoadedProfile, SecretsService};
19use systemprompt_manifest::Config;
20use systemprompt_manifest::profile::Profile;
21use systemprompt_manifest::services::ServicesConfig;
22use systemprompt_models::errors::GlobalConfigError;
23
24use crate::bundle::{BundleResult, ServicesSourceBootstrap};
25use crate::config_loader::ConfigLoader;
26use crate::error::ConfigLoadResult;
27use systemprompt_manifest::services::root::ActiveServicesRoot;
28
29#[derive(Clone)]
30pub struct BootContext {
31 inner: Arc<BootInner>,
32}
33
34struct BootInner {
35 profile: Arc<LoadedProfile>,
36 secrets: Arc<SecretsService>,
37 config: OnceLock<Arc<Config>>,
38 services_root: OnceLock<ActiveServicesRoot>,
39}
40
41impl std::fmt::Debug for BootContext {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.debug_struct("BootContext")
44 .field("profile", &self.inner.profile.path())
45 .field("config", &self.inner.config.get().is_some())
46 .field("services_root", &self.inner.services_root.get())
47 .finish_non_exhaustive()
48 }
49}
50
51impl BootContext {
52 #[must_use]
53 pub fn new(profile: Arc<LoadedProfile>, secrets: Arc<SecretsService>) -> Self {
54 Self {
55 inner: Arc::new(BootInner {
56 profile,
57 secrets,
58 config: OnceLock::new(),
59 services_root: OnceLock::new(),
60 }),
61 }
62 }
63
64 #[must_use]
65 pub fn profile(&self) -> &Profile {
66 self.inner.profile.profile()
67 }
68
69 #[must_use]
70 pub fn loaded_profile(&self) -> &Arc<LoadedProfile> {
71 &self.inner.profile
72 }
73
74 #[must_use]
75 pub fn profile_path(&self) -> &Path {
76 self.inner.profile.path()
77 }
78
79 #[must_use]
80 pub fn secrets(&self) -> &Arc<SecretsService> {
81 &self.inner.secrets
82 }
83
84 pub fn config(&self) -> Result<&Arc<Config>, GlobalConfigError> {
85 self.inner
86 .config
87 .get()
88 .ok_or(GlobalConfigError::NotInitialized)
89 }
90
91 #[must_use]
92 pub fn is_config_initialized(&self) -> bool {
93 self.inner.config.get().is_some()
94 }
95
96 pub fn try_init_config(&self, services_root: Option<&Path>) -> ConfigResult<&Arc<Config>> {
97 if let Some(config) = self.inner.config.get() {
98 return Ok(config);
99 }
100 let config = systemprompt_config::build_from_profile(
101 &self.inner.profile,
102 &self.inner.secrets.get(),
103 services_root,
104 )?;
105 self.install_config(config)
106 }
107
108 pub fn install_config(&self, config: Config) -> ConfigResult<&Arc<Config>> {
109 self.inner
110 .config
111 .set(Arc::new(config))
112 .map_err(|_config| ConfigError::AlreadyInitialized)?;
113 self.inner
114 .config
115 .get()
116 .ok_or_else(|| ConfigError::AlreadyInitialized)
117 }
118
119 #[must_use]
120 pub fn services_root(&self) -> Option<&ActiveServicesRoot> {
121 self.inner.services_root.get()
122 }
123
124 pub fn require_services_root(&self) -> ActiveServicesRoot {
125 self.inner
126 .services_root
127 .get()
128 .cloned()
129 .unwrap_or_else(|| ActiveServicesRoot::baked(self.profile()))
130 }
131
132 pub fn install_services_root(&self, root: ActiveServicesRoot) -> &ActiveServicesRoot {
133 self.inner.services_root.get_or_init(|| root)
134 }
135
136 pub async fn try_resolve_services_root(
137 &self,
138 core_version: &str,
139 ) -> BundleResult<&ActiveServicesRoot> {
140 if let Some(active) = self.inner.services_root.get() {
141 return Ok(active);
142 }
143 let secrets = self.inner.secrets.get();
144 let root = ServicesSourceBootstrap::resolve(
145 self.profile(),
146 |name| secrets.get(name).cloned(),
147 core_version,
148 )
149 .await?;
150 Ok(self.install_services_root(root))
151 }
152
153 pub fn load_services_config(&self) -> ConfigLoadResult<ServicesConfig> {
154 self.config_loader().load()
155 }
156
157 #[must_use]
158 pub fn config_loader(&self) -> ConfigLoader {
159 ConfigLoader::for_profile(self.profile(), &self.require_services_root().path)
160 }
161}