Skip to main content

systemprompt_cloud/profile_authoring/
local_builder.rs

1//! Builder for the local-development [`Profile`].
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use std::path::Path;
7
8use systemprompt_identifiers::{Email, TenantId};
9use systemprompt_loader::ExtensionLoader;
10use systemprompt_manifest::profile::{
11    ProfileResult, SecretsConfig, SecretsSource, SecretsValidationMode,
12};
13use systemprompt_manifest::services::SystemAdminConfig;
14use systemprompt_manifest::{
15    CloudConfig, CloudValidationMode, ExtensionsConfig, HostingProvider, PathsConfig, Profile,
16    ProfileDatabaseConfig, ProfileType, RateLimitsConfig, ServerConfig, SiteConfig,
17};
18
19use super::{generate_display_name, local_runtime_config, security_config, webhook_governance};
20use crate::constants::profile as consts;
21use crate::paths::ProjectContext;
22
23#[derive(Debug)]
24pub struct LocalProfileBuilder {
25    name: String,
26    tenant_id: Option<TenantId>,
27    secrets_path: String,
28    services_path: String,
29}
30
31impl LocalProfileBuilder {
32    pub fn new(
33        name: impl Into<String>,
34        secrets_path: impl AsRef<Path>,
35        services_path: impl AsRef<Path>,
36    ) -> Self {
37        Self {
38            name: name.into(),
39            tenant_id: None,
40            secrets_path: secrets_path.as_ref().to_string_lossy().to_string(),
41            services_path: services_path.as_ref().to_string_lossy().to_string(),
42        }
43    }
44
45    #[must_use]
46    pub fn with_tenant_id(mut self, tenant_id: TenantId) -> Self {
47        self.tenant_id = Some(tenant_id);
48        self
49    }
50
51    pub fn build(self) -> ProfileResult<Profile> {
52        let ctx = ProjectContext::discover();
53        let root = ctx.root();
54        let system_path = root.to_string_lossy().to_string();
55        let display_name = generate_display_name(&self.name);
56        let local_url = format!("http://localhost:{}", consts::DEFAULT_PORT);
57
58        let mut profile = Profile {
59            storage: systemprompt_manifest::profile::StorageConfig::default(),
60            observability: systemprompt_manifest::profile::ObservabilityConfig::default(),
61            config_planes: systemprompt_manifest::profile::ConfigPlanesConfig::default(),
62            licence: systemprompt_manifest::profile::LicenceConfig::default(),
63            notifications: systemprompt_manifest::profile::NotificationsProfile::default(),
64            brain: systemprompt_manifest::profile::BrainProfile::default(),
65            name: self.name,
66            display_name,
67            target: ProfileType::Local,
68            site: SiteConfig {
69                name: "systemprompt.io".to_owned(),
70                github_link: None,
71            },
72            database: ProfileDatabaseConfig {
73                db_type: consts::DEFAULT_DB_TYPE.to_owned(),
74                external_db_access: false,
75                migrate_on_boot: true,
76                pool: None,
77            },
78            security: security_config(Vec::new(), true),
79            server: local_server_config(local_url),
80            paths: PathsConfig {
81                root: None,
82                system: system_path,
83                services: self.services_path,
84                bin: ExtensionLoader::resolve_bin_directory(root, None)
85                    .to_string_lossy()
86                    .to_string(),
87                storage: Some(ctx.storage_dir().to_string_lossy().to_string()),
88                geoip_database: None,
89                data: None,
90                web_path: None,
91                shipped_files: None,
92            },
93            rate_limits: RateLimitsConfig {
94                disabled: true,
95                ..Default::default()
96            },
97            runtime: local_runtime_config(),
98            cloud: Some(CloudConfig {
99                tenant_id: self.tenant_id,
100                provider: HostingProvider::DockerLocal,
101                validation: CloudValidationMode::Warn,
102                deploy: None,
103                operator: None,
104                image: None,
105            }),
106            secrets: Some(SecretsConfig {
107                secrets_path: Some(self.secrets_path),
108                validation: SecretsValidationMode::Warn,
109                source: SecretsSource::File,
110                vault: None,
111                external: None,
112                refresh_secs: None,
113            }),
114            extensions: ExtensionsConfig::default(),
115            governance: Some(webhook_governance()),
116            services: systemprompt_manifest::profile::ServicesProfileConfig::default(),
117            judge: systemprompt_manifest::profile::JudgeProfile::default(),
118            retention: systemprompt_manifest::profile::RetentionConfig::default(),
119            system_admin: SystemAdminConfig {
120                username: "admin".to_owned(),
121                email: Some(Email::local_admin()),
122            },
123        };
124        profile.derive_fields()?;
125        Ok(profile)
126    }
127}
128
129fn local_server_config(local_url: String) -> ServerConfig {
130    let mut server = ServerConfig::new(consts::LOCAL_HOST, consts::DEFAULT_PORT, local_url);
131    server.extra_cors_origins = vec!["http://localhost:5173".to_owned()];
132    server.role = systemprompt_manifest::profile::NodeRole::All;
133    server.trusted_proxies =
134        crate::trusted_proxies::default_trusted_proxies(HostingProvider::DockerLocal);
135    server
136}