systemprompt_runtime/builder/
mod.rs1mod assembly;
9mod core_layer;
10
11use std::sync::{Arc, OnceLock};
12
13use systemprompt_analytics::{AnalyticsService, FingerprintRepository};
14use systemprompt_database::MigrationConfig;
15use systemprompt_extension::ExtensionRegistry;
16use systemprompt_marketplace::MarketplaceFilter;
17use systemprompt_mcp::services::registry::RegistryService;
18use systemprompt_security::authz::{AuthzDecisionHook, SharedAuthzHook};
19use systemprompt_users::UserService;
20
21use crate::context::{AppContext, ConfigPlane, DataPlane, Plugins, Subsystems};
22use crate::error::RuntimeResult;
23use crate::registry::ModuleApiRegistry;
24use core_layer::{CoreLayer, init_core, init_extensions};
25
26#[derive(Default)]
35pub struct AppContextBuilder {
36 extension_registry: Option<ExtensionRegistry>,
37 show_startup_warnings: bool,
38 marketplace_filter: Option<Arc<dyn MarketplaceFilter>>,
39 authz_hook: Option<SharedAuthzHook>,
40 install_schemas: bool,
41 migration_config: MigrationConfig,
42}
43
44impl std::fmt::Debug for AppContextBuilder {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.debug_struct("AppContextBuilder")
47 .field("extension_registry", &self.extension_registry.is_some())
48 .field("show_startup_warnings", &self.show_startup_warnings)
49 .field("marketplace_filter", &self.marketplace_filter.is_some())
50 .field("authz_hook", &self.authz_hook.is_some())
51 .field("install_schemas", &self.install_schemas)
52 .field("migration_config", &self.migration_config)
53 .finish()
54 }
55}
56
57impl AppContextBuilder {
58 #[must_use]
59 pub fn new() -> Self {
60 Self::default()
61 }
62
63 #[must_use]
66 pub fn with_extensions(mut self, registry: ExtensionRegistry) -> Self {
67 self.extension_registry = Some(registry);
68 self
69 }
70
71 #[must_use]
72 pub const fn with_startup_warnings(mut self, show: bool) -> Self {
73 self.show_startup_warnings = show;
74 self
75 }
76
77 #[must_use]
81 pub fn with_marketplace_filter(mut self, filter: Arc<dyn MarketplaceFilter>) -> Self {
82 self.marketplace_filter = Some(filter);
83 self
84 }
85
86 #[must_use]
90 pub const fn with_migrations(mut self, install: bool) -> Self {
91 self.install_schemas = install;
92 self
93 }
94
95 #[must_use]
99 pub fn with_authz_hook<H>(mut self, hook: H) -> Self
100 where
101 H: AuthzDecisionHook + 'static,
102 {
103 self.authz_hook = Some(Arc::new(hook));
104 self
105 }
106
107 #[must_use]
113 pub fn with_shared_authz_hook(mut self, hook: SharedAuthzHook) -> Self {
114 self.authz_hook = Some(hook);
115 self
116 }
117
118 #[must_use]
119 pub const fn with_migration_config(mut self, config: MigrationConfig) -> Self {
120 self.migration_config = config;
121 self
122 }
123
124 pub async fn build(self) -> RuntimeResult<AppContext> {
125 let CoreLayer {
126 config,
127 app_paths,
128 database,
129 authz_hook,
130 } = init_core(self.authz_hook).await?;
131
132 let api_registry = Arc::new(ModuleApiRegistry::new());
133 let extension_registry = init_extensions(
134 self.extension_registry,
135 self.install_schemas,
136 self.migration_config,
137 &database,
138 )
139 .await?;
140
141 let geoip_reader = AppContext::load_geoip_database(&config, self.show_startup_warnings);
142 let content_config = AppContext::load_content_config(&config, &app_paths);
143 let content_routing = assembly::content_routing_from(content_config.as_ref());
144 let route_classifier = Arc::new(systemprompt_models::RouteClassifier::new(
145 content_routing.clone(),
146 ));
147 let analytics_service = Arc::new(AnalyticsService::new(
148 &database,
149 geoip_reader.clone(),
150 content_routing,
151 )?);
152
153 let fingerprint_repo = match FingerprintRepository::new(&database) {
154 Ok(repo) => Some(Arc::new(repo)),
155 Err(e) => {
156 tracing::warn!(error = %e, "Failed to initialize fingerprint repository");
157 None
158 },
159 };
160
161 let user_service = Arc::new(UserService::new(&database)?);
165
166 let system_admin =
167 assembly::resolve_and_install_system_admin(&config, &user_service).await?;
168 let mcp_registry = RegistryService::new(system_admin.id().clone());
169
170 let marketplace_filter = self
171 .marketplace_filter
172 .unwrap_or_else(|| assembly::build_marketplace_filter(&database));
173
174 let event_bridge = Arc::new(OnceLock::new());
175
176 Ok(AppContext::from_parts(
177 DataPlane {
178 database,
179 analytics_service,
180 fingerprint_repo,
181 user_service: Some(user_service),
182 },
183 ConfigPlane {
184 config,
185 app_paths,
186 content_config,
187 route_classifier,
188 },
189 Plugins {
190 extension_registry,
191 api_registry,
192 mcp_registry,
193 marketplace_filter,
194 },
195 Subsystems {
196 system_admin,
197 authz_hook,
198 event_bridge,
199 geoip_reader,
200 },
201 ))
202 }
203}