Skip to main content

systemprompt_runtime/
builder.rs

1use anyhow::Result;
2use std::sync::Arc;
3
4use systemprompt_analytics::{AnalyticsService, FingerprintRepository};
5use systemprompt_config::ProfileBootstrap;
6use systemprompt_database::Database;
7use systemprompt_extension::ExtensionRegistry;
8use systemprompt_models::{AppPaths, Config, ContentConfigRaw, ContentRouting};
9use systemprompt_users::UserService;
10
11use crate::context::{AppContext, AppContextParts};
12use crate::registry::ModuleApiRegistry;
13
14#[derive(Debug, Default)]
15pub struct AppContextBuilder {
16    extension_registry: Option<ExtensionRegistry>,
17    show_startup_warnings: bool,
18}
19
20impl AppContextBuilder {
21    #[must_use]
22    pub fn new() -> Self {
23        Self::default()
24    }
25
26    #[must_use]
27    pub fn with_extensions(mut self, registry: ExtensionRegistry) -> Self {
28        self.extension_registry = Some(registry);
29        self
30    }
31
32    #[must_use]
33    pub const fn with_startup_warnings(mut self, show: bool) -> Self {
34        self.show_startup_warnings = show;
35        self
36    }
37
38    pub async fn build(self) -> Result<AppContext> {
39        let profile = ProfileBootstrap::get()?;
40        let app_paths = Arc::new(AppPaths::from_profile(&profile.paths)?);
41        systemprompt_files::FilesConfig::init(&app_paths)?;
42        let config = Arc::new(Config::get()?.clone());
43
44        let database = Arc::new(
45            Database::from_config_with_write(
46                &config.database_type,
47                &config.database_url,
48                config.database_write_url.as_deref(),
49            )
50            .await?,
51        );
52
53        systemprompt_logging::init_logging(Arc::clone(&database));
54
55        if config.database_write_url.is_some() {
56            tracing::info!(
57                "Database read/write separation enabled: reads from replica, writes to primary"
58            );
59        }
60
61        let api_registry = Arc::new(ModuleApiRegistry::new());
62
63        let registry = self
64            .extension_registry
65            .unwrap_or_else(ExtensionRegistry::discover);
66        registry.validate()?;
67        let extension_registry = Arc::new(registry);
68
69        let geoip_reader = AppContext::load_geoip_database(&config, self.show_startup_warnings);
70        let content_config = AppContext::load_content_config(&config, &app_paths);
71        let content_routing = content_routing_from(content_config.as_ref());
72        let route_classifier = Arc::new(systemprompt_models::RouteClassifier::new(
73            content_routing.clone(),
74        ));
75        let analytics_service = Arc::new(AnalyticsService::new(
76            &database,
77            geoip_reader.clone(),
78            content_routing,
79        )?);
80
81        let fingerprint_repo = match FingerprintRepository::new(&database) {
82            Ok(repo) => Some(Arc::new(repo)),
83            Err(e) => {
84                tracing::warn!(error = %e, "Failed to initialize fingerprint repository");
85                None
86            },
87        };
88
89        let user_service = match UserService::new(&database) {
90            Ok(svc) => Some(Arc::new(svc)),
91            Err(e) => {
92                tracing::warn!(error = %e, "Failed to initialize user service");
93                None
94            },
95        };
96
97        Ok(AppContext::from_parts(AppContextParts {
98            config,
99            database,
100            api_registry,
101            extension_registry,
102            geoip_reader,
103            content_config,
104            route_classifier,
105            analytics_service,
106            fingerprint_repo,
107            user_service,
108            app_paths,
109        }))
110    }
111}
112
113#[allow(trivial_casts)]
114fn content_routing_from(
115    content_config: Option<&Arc<ContentConfigRaw>>,
116) -> Option<Arc<dyn ContentRouting>> {
117    content_config
118        .cloned()
119        .map(|c| c as Arc<dyn ContentRouting>)
120}