Skip to main content

systemprompt_runtime/
builder.rs

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