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::{SecretsConfig, SecretsSource, SecretsValidationMode};
11use systemprompt_manifest::services::SystemAdminConfig;
12use systemprompt_manifest::{
13    CloudConfig, CloudValidationMode, ContentNegotiationConfig, ExtensionsConfig, PathsConfig,
14    Profile, ProfileDatabaseConfig, ProfileType, RateLimitsConfig, SecurityHeadersConfig,
15    ServerConfig, SiteConfig,
16};
17
18use super::{generate_display_name, local_runtime_config, security_config, webhook_governance};
19use crate::constants::profile as consts;
20use crate::paths::ProjectContext;
21
22#[derive(Debug)]
23pub struct LocalProfileBuilder {
24    name: String,
25    tenant_id: Option<TenantId>,
26    secrets_path: String,
27    services_path: String,
28}
29
30impl LocalProfileBuilder {
31    pub fn new(
32        name: impl Into<String>,
33        secrets_path: impl AsRef<Path>,
34        services_path: impl AsRef<Path>,
35    ) -> Self {
36        Self {
37            name: name.into(),
38            tenant_id: None,
39            secrets_path: secrets_path.as_ref().to_string_lossy().to_string(),
40            services_path: services_path.as_ref().to_string_lossy().to_string(),
41        }
42    }
43
44    #[must_use]
45    pub fn with_tenant_id(mut self, tenant_id: TenantId) -> Self {
46        self.tenant_id = Some(tenant_id);
47        self
48    }
49
50    #[must_use]
51    pub fn build(self) -> 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        let internal_url = local_url.clone();
58
59        Profile {
60            storage: systemprompt_manifest::profile::StorageConfig::default(),
61            observability: systemprompt_manifest::profile::ObservabilityConfig::default(),
62            config_planes: systemprompt_manifest::profile::ConfigPlanesConfig::default(),
63            name: self.name,
64            display_name,
65            target: ProfileType::Local,
66            site: SiteConfig {
67                name: "systemprompt.io".to_owned(),
68                github_link: None,
69            },
70            database: ProfileDatabaseConfig {
71                db_type: consts::DEFAULT_DB_TYPE.to_owned(),
72                external_db_access: false,
73                migrate_on_boot: true,
74                pool: None,
75            },
76            security: security_config(&local_url, Vec::new(), true),
77            server: local_server_config(local_url),
78            paths: PathsConfig {
79                system: system_path,
80                services: self.services_path,
81                bin: ExtensionLoader::resolve_bin_directory(root, None)
82                    .to_string_lossy()
83                    .to_string(),
84                storage: Some(ctx.storage_dir().to_string_lossy().to_string()),
85                geoip_database: None,
86                data: None,
87                web_path: None,
88                shipped_files: None,
89            },
90            rate_limits: RateLimitsConfig {
91                disabled: true,
92                ..Default::default()
93            },
94            runtime: local_runtime_config(),
95            cloud: Some(CloudConfig {
96                tenant_id: self.tenant_id,
97                validation: CloudValidationMode::Warn,
98            }),
99            secrets: Some(SecretsConfig {
100                secrets_path: Some(self.secrets_path),
101                validation: SecretsValidationMode::Warn,
102                source: SecretsSource::File,
103                vault: None,
104                external: None,
105                refresh_secs: None,
106            }),
107            extensions: ExtensionsConfig::default(),
108            governance: Some(webhook_governance(&internal_url)),
109            services: systemprompt_manifest::profile::ServicesProfileConfig::default(),
110            judge: systemprompt_manifest::profile::JudgeProfile::default(),
111            retention: systemprompt_manifest::profile::RetentionConfig::default(),
112            system_admin: SystemAdminConfig {
113                username: "admin".to_owned(),
114                email: Some(Email::local_admin()),
115            },
116        }
117    }
118}
119
120fn local_server_config(local_url: String) -> ServerConfig {
121    ServerConfig {
122        host: consts::LOCAL_HOST.to_owned(),
123        port: consts::DEFAULT_PORT,
124        api_server_url: local_url.clone(),
125        api_internal_url: local_url.clone(),
126        api_external_url: local_url.clone(),
127        use_https: false,
128        cors_allowed_origins: vec![local_url, "http://localhost:5173".to_owned()],
129        content_negotiation: ContentNegotiationConfig::default(),
130        security_headers: SecurityHeadersConfig::default(),
131        instance_id: None,
132        metrics_port: None,
133        max_concurrent_streams: systemprompt_manifest::config::DEFAULT_MAX_CONCURRENT_STREAMS,
134        role: systemprompt_manifest::profile::NodeRole::All,
135        max_in_flight: None,
136        shutdown: systemprompt_manifest::profile::ShutdownConfig::default(),
137        trusted_proxies: crate::trusted_proxies::default_local_trusted_proxies(),
138    }
139}