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