systemprompt-cloud 0.76.0

Cloud API client, credentials, OAuth, and tenant management for systemprompt.io AI governance deployments. Remote sync and multi-tenant orchestration for the MCP governance pipeline.
Documentation
//! Builder for the local-development [`Profile`].
//!
//! Copyright (c) systemprompt.io — Business Source License 1.1.
//! See <https://systemprompt.io> for licensing details.

use std::path::Path;

use systemprompt_identifiers::{Email, TenantId};
use systemprompt_loader::ExtensionLoader;
use systemprompt_manifest::profile::{
    ProfileResult, SecretsConfig, SecretsSource, SecretsValidationMode,
};
use systemprompt_manifest::services::SystemAdminConfig;
use systemprompt_manifest::{
    CloudConfig, CloudValidationMode, ExtensionsConfig, HostingProvider, PathsConfig, Profile,
    ProfileDatabaseConfig, ProfileType, RateLimitsConfig, ServerConfig, SiteConfig,
};

use super::{generate_display_name, local_runtime_config, security_config, webhook_governance};
use crate::constants::profile as consts;
use crate::paths::ProjectContext;

#[derive(Debug)]
pub struct LocalProfileBuilder {
    name: String,
    tenant_id: Option<TenantId>,
    secrets_path: String,
    services_path: String,
}

impl LocalProfileBuilder {
    pub fn new(
        name: impl Into<String>,
        secrets_path: impl AsRef<Path>,
        services_path: impl AsRef<Path>,
    ) -> Self {
        Self {
            name: name.into(),
            tenant_id: None,
            secrets_path: secrets_path.as_ref().to_string_lossy().to_string(),
            services_path: services_path.as_ref().to_string_lossy().to_string(),
        }
    }

    #[must_use]
    pub fn with_tenant_id(mut self, tenant_id: TenantId) -> Self {
        self.tenant_id = Some(tenant_id);
        self
    }

    pub fn build(self) -> ProfileResult<Profile> {
        let ctx = ProjectContext::discover();
        let root = ctx.root();
        let system_path = root.to_string_lossy().to_string();
        let display_name = generate_display_name(&self.name);
        let local_url = format!("http://localhost:{}", consts::DEFAULT_PORT);

        let mut profile = Profile {
            storage: systemprompt_manifest::profile::StorageConfig::default(),
            observability: systemprompt_manifest::profile::ObservabilityConfig::default(),
            config_planes: systemprompt_manifest::profile::ConfigPlanesConfig::default(),
            licence: systemprompt_manifest::profile::LicenceConfig::default(),
            notifications: systemprompt_manifest::profile::NotificationsProfile::default(),
            name: self.name,
            display_name,
            target: ProfileType::Local,
            site: SiteConfig {
                name: "systemprompt.io".to_owned(),
                github_link: None,
            },
            database: ProfileDatabaseConfig {
                db_type: consts::DEFAULT_DB_TYPE.to_owned(),
                external_db_access: false,
                migrate_on_boot: true,
                pool: None,
            },
            security: security_config(Vec::new(), true),
            server: local_server_config(local_url),
            paths: PathsConfig {
                root: None,
                system: system_path,
                services: self.services_path,
                bin: ExtensionLoader::resolve_bin_directory(root, None)
                    .to_string_lossy()
                    .to_string(),
                storage: Some(ctx.storage_dir().to_string_lossy().to_string()),
                geoip_database: None,
                data: None,
                web_path: None,
                shipped_files: None,
            },
            rate_limits: RateLimitsConfig {
                disabled: true,
                ..Default::default()
            },
            runtime: local_runtime_config(),
            cloud: Some(CloudConfig {
                tenant_id: self.tenant_id,
                provider: HostingProvider::DockerLocal,
                validation: CloudValidationMode::Warn,
                deploy: None,
                operator: None,
            }),
            secrets: Some(SecretsConfig {
                secrets_path: Some(self.secrets_path),
                validation: SecretsValidationMode::Warn,
                source: SecretsSource::File,
                vault: None,
                external: None,
                refresh_secs: None,
            }),
            extensions: ExtensionsConfig::default(),
            governance: Some(webhook_governance()),
            services: systemprompt_manifest::profile::ServicesProfileConfig::default(),
            judge: systemprompt_manifest::profile::JudgeProfile::default(),
            retention: systemprompt_manifest::profile::RetentionConfig::default(),
            system_admin: SystemAdminConfig {
                username: "admin".to_owned(),
                email: Some(Email::local_admin()),
            },
        };
        profile.derive_fields()?;
        Ok(profile)
    }
}

fn local_server_config(local_url: String) -> ServerConfig {
    let mut server = ServerConfig::new(consts::LOCAL_HOST, consts::DEFAULT_PORT, local_url);
    server.extra_cors_origins = vec!["http://localhost:5173".to_owned()];
    server.role = systemprompt_manifest::profile::NodeRole::All;
    server.trusted_proxies =
        crate::trusted_proxies::default_trusted_proxies(HostingProvider::DockerLocal);
    server
}