Skip to main content

systemprompt_runtime/context/
mod.rs

1//! [`AppContext`] — the application-wide runtime container.
2//!
3//! Holds shared handles (config, database pool, extension registry,
4//! analytics, route classifier, etc.) cloned cheaply via [`Arc`].
5//! Constructed via [`crate::AppContextBuilder`] or [`AppContext::new`].
6//!
7//! Copyright (c) systemprompt.io — Business Source License 1.1.
8//! See <https://systemprompt.io> for licensing details.
9
10use std::sync::{Arc, OnceLock};
11
12use tokio::task::JoinHandle;
13
14use systemprompt_agent::repository::A2ARepositories;
15use systemprompt_ai::repository::AiRepositories;
16use systemprompt_analytics::repository::AnalyticsRepositories;
17use systemprompt_analytics::{AnalyticsService, FingerprintRepository, GeoIpReader};
18use systemprompt_content::repository::ContentRepositories;
19use systemprompt_database::{DbPool, ServiceRepository};
20use systemprompt_extension::ExtensionRegistry;
21use systemprompt_files::FileRepository;
22use systemprompt_marketplace::MarketplaceFilter;
23use systemprompt_mcp::repository::McpSessionRepository;
24use systemprompt_mcp::services::registry::RegistryService;
25use systemprompt_models::services::SystemAdmin;
26use systemprompt_models::{AppPaths, Config, ContentConfigRaw, ContentRouting, RouteClassifier};
27use systemprompt_oauth::repository::OAuthRepositories;
28use systemprompt_security::authz::SharedAuthzHook;
29use systemprompt_users::{UserRepository, UserService};
30
31mod context_loaders;
32mod debug_impls;
33
34use crate::builder::AppContextBuilder;
35use crate::error::RuntimeResult;
36use crate::registry::ModuleApiRegistry;
37
38/// Database pool and the data-access services layered on it.
39///
40/// `fingerprint_repo` and `user_service` are `None` when the corresponding
41/// resource failed to initialise; callers must degrade gracefully.
42#[derive(Clone)]
43pub struct DataPlane {
44    pub database: DbPool,
45    pub analytics_service: Arc<AnalyticsService>,
46    pub fingerprint_repo: Option<Arc<FingerprintRepository>>,
47    pub user_service: Option<Arc<UserService>>,
48    pub a2a_repositories: Arc<A2ARepositories>,
49    pub content_repositories: Arc<ContentRepositories>,
50    pub oauth_repositories: Arc<OAuthRepositories>,
51    pub user_repository: Arc<UserRepository>,
52    pub service_repository: Arc<ServiceRepository>,
53    pub ai_repositories: Arc<AiRepositories>,
54    pub analytics_repositories: Arc<AnalyticsRepositories>,
55    pub file_repository: Arc<FileRepository>,
56    pub mcp_session_repository: Arc<McpSessionRepository>,
57}
58
59#[derive(Clone)]
60pub struct ConfigPlane {
61    pub config: Arc<Config>,
62    pub app_paths: Arc<AppPaths>,
63    pub content_config: Option<Arc<ContentConfigRaw>>,
64    pub route_classifier: Arc<RouteClassifier>,
65}
66
67#[derive(Clone)]
68pub struct Plugins {
69    pub extension_registry: Arc<ExtensionRegistry>,
70    pub api_registry: Arc<ModuleApiRegistry>,
71    pub mcp_registry: RegistryService,
72    pub marketplace_filter: Arc<dyn MarketplaceFilter>,
73}
74
75#[derive(Clone)]
76pub struct Subsystems {
77    pub system_admin: Arc<SystemAdmin>,
78    pub authz_hook: SharedAuthzHook,
79    pub event_bridge: Arc<OnceLock<JoinHandle<()>>>,
80    pub geoip_reader: Option<GeoIpReader>,
81}
82
83/// Application-wide runtime container shared across the HTTP server, the
84/// scheduler, and CLI commands.
85///
86/// Handles are grouped into four cohesive planes ([`DataPlane`],
87/// [`ConfigPlane`], [`Plugins`], [`Subsystems`]); each field is an [`Arc`] (or
88/// an `Arc`-internal handle such as [`DbPool`]), so `clone` is a
89/// reference-count bump rather than a deep copy. Construct it via
90/// [`AppContext::builder`] (or [`AppContext::new`] for the default build);
91/// [`AppContext::from_parts`] bypasses the bootstrap and is intended for tests
92/// and embedders that assemble the planes themselves. Read individual handles
93/// through the accessor methods.
94#[derive(Clone)]
95pub struct AppContext {
96    pub(crate) data: DataPlane,
97    pub(crate) cfg: ConfigPlane,
98    pub(crate) plugins: Plugins,
99    pub(crate) subsystems: Subsystems,
100}
101
102impl AppContext {
103    pub async fn new() -> RuntimeResult<Self> {
104        Self::builder().build().await
105    }
106
107    #[must_use]
108    pub fn builder() -> AppContextBuilder {
109        AppContextBuilder::new()
110    }
111
112    #[must_use]
113    pub const fn from_parts(
114        data: DataPlane,
115        cfg: ConfigPlane,
116        plugins: Plugins,
117        subsystems: Subsystems,
118    ) -> Self {
119        Self {
120            data,
121            cfg,
122            plugins,
123            subsystems,
124        }
125    }
126
127    pub fn load_geoip_database(
128        config: &Config,
129        show_warnings: bool,
130    ) -> Result<Option<GeoIpReader>, crate::error::RuntimeError> {
131        context_loaders::load_geoip_database(config, show_warnings)
132    }
133
134    pub fn load_content_config(
135        config: &Config,
136        app_paths: &AppPaths,
137    ) -> Option<Arc<ContentConfigRaw>> {
138        context_loaders::load_content_config(config, app_paths)
139    }
140
141    pub fn config(&self) -> &Config {
142        &self.cfg.config
143    }
144
145    pub fn content_config(&self) -> Option<&ContentConfigRaw> {
146        self.cfg.content_config.as_ref().map(AsRef::as_ref)
147    }
148
149    pub fn content_routing(&self) -> Option<Arc<dyn ContentRouting>> {
150        let concrete = Arc::clone(self.cfg.content_config.as_ref()?);
151        let routing: Arc<dyn ContentRouting> = concrete;
152        Some(routing)
153    }
154
155    pub const fn db_pool(&self) -> &DbPool {
156        &self.data.database
157    }
158
159    pub fn api_registry(&self) -> &ModuleApiRegistry {
160        &self.plugins.api_registry
161    }
162
163    pub fn extension_registry(&self) -> &ExtensionRegistry {
164        &self.plugins.extension_registry
165    }
166
167    pub fn server_address(&self) -> String {
168        format!("{}:{}", self.cfg.config.host, self.cfg.config.port)
169    }
170
171    pub const fn geoip_reader(&self) -> Option<&GeoIpReader> {
172        self.subsystems.geoip_reader.as_ref()
173    }
174
175    pub const fn analytics_service(&self) -> &Arc<AnalyticsService> {
176        &self.data.analytics_service
177    }
178
179    #[must_use]
180    pub fn session_usage(&self) -> systemprompt_traits::DynSessionUsageCounters {
181        Arc::new(self.data.analytics_service.session_repo().clone())
182    }
183
184    pub fn context_materializer(&self) -> systemprompt_traits::DynContextMaterializer {
185        Arc::new(systemprompt_agent::services::ContextProviderService::new(
186            self.data.a2a_repositories.contexts.clone(),
187        ))
188    }
189
190    pub const fn a2a_repositories(&self) -> &Arc<A2ARepositories> {
191        &self.data.a2a_repositories
192    }
193
194    pub const fn content_repositories(&self) -> &Arc<ContentRepositories> {
195        &self.data.content_repositories
196    }
197
198    pub const fn oauth_repositories(&self) -> &Arc<OAuthRepositories> {
199        &self.data.oauth_repositories
200    }
201
202    pub const fn user_repository(&self) -> &Arc<UserRepository> {
203        &self.data.user_repository
204    }
205
206    pub const fn service_repository(&self) -> &Arc<ServiceRepository> {
207        &self.data.service_repository
208    }
209
210    pub const fn ai_repositories(&self) -> &Arc<AiRepositories> {
211        &self.data.ai_repositories
212    }
213
214    pub const fn analytics_repositories(&self) -> &Arc<AnalyticsRepositories> {
215        &self.data.analytics_repositories
216    }
217
218    pub const fn file_repository(&self) -> &Arc<FileRepository> {
219        &self.data.file_repository
220    }
221
222    pub const fn mcp_session_repository(&self) -> &Arc<McpSessionRepository> {
223        &self.data.mcp_session_repository
224    }
225
226    pub const fn route_classifier(&self) -> &Arc<RouteClassifier> {
227        &self.cfg.route_classifier
228    }
229
230    pub fn app_paths(&self) -> &AppPaths {
231        &self.cfg.app_paths
232    }
233
234    pub const fn app_paths_arc(&self) -> &Arc<AppPaths> {
235        &self.cfg.app_paths
236    }
237
238    pub fn marketplace_filter(&self) -> &Arc<dyn MarketplaceFilter> {
239        &self.plugins.marketplace_filter
240    }
241
242    pub const fn event_bridge(&self) -> &Arc<OnceLock<JoinHandle<()>>> {
243        &self.subsystems.event_bridge
244    }
245
246    pub fn system_admin(&self) -> &SystemAdmin {
247        &self.subsystems.system_admin
248    }
249
250    pub const fn mcp_registry(&self) -> &RegistryService {
251        &self.plugins.mcp_registry
252    }
253
254    pub const fn authz_hook(&self) -> &SharedAuthzHook {
255        &self.subsystems.authz_hook
256    }
257}