Skip to main content

systemprompt_cloud/
context.rs

1use systemprompt_models::profile_bootstrap::ProfileBootstrap;
2use systemprompt_models::Profile;
3
4use crate::api_client::CloudApiClient;
5use crate::credentials::CloudCredentials;
6use crate::error::{CloudError, CloudResult};
7use crate::paths::{get_cloud_paths, CloudPath};
8use crate::tenants::{StoredTenant, TenantStore};
9
10#[derive(Debug, Clone)]
11pub struct ResolvedTenant {
12    pub id: String,
13    pub name: String,
14    pub app_id: Option<String>,
15    pub hostname: Option<String>,
16    pub region: Option<String>,
17}
18
19impl From<StoredTenant> for ResolvedTenant {
20    fn from(tenant: StoredTenant) -> Self {
21        Self {
22            id: tenant.id,
23            name: tenant.name,
24            app_id: tenant.app_id,
25            hostname: tenant.hostname,
26            region: tenant.region,
27        }
28    }
29}
30
31#[derive(Debug)]
32pub struct CloudContext {
33    pub credentials: CloudCredentials,
34    pub profile: Option<&'static Profile>,
35    pub tenant: Option<ResolvedTenant>,
36    pub api_client: CloudApiClient,
37}
38
39impl CloudContext {
40    pub fn new_authenticated() -> CloudResult<Self> {
41        let cloud_paths = get_cloud_paths().map_err(|_| CloudError::NotAuthenticated)?;
42        let creds_path = cloud_paths.resolve(CloudPath::Credentials);
43        let credentials = CloudCredentials::load_and_validate_from_path(&creds_path)
44            .map_err(|_| CloudError::NotAuthenticated)?;
45
46        let api_client = CloudApiClient::new(&credentials.api_url, &credentials.api_token)
47            .map_err(CloudError::Network)?;
48
49        Ok(Self {
50            credentials,
51            profile: None,
52            tenant: None,
53            api_client,
54        })
55    }
56
57    pub fn with_profile(mut self) -> CloudResult<Self> {
58        let profile = ProfileBootstrap::get().map_err(|e| CloudError::ProfileRequired {
59            message: e.to_string(),
60        })?;
61
62        self.profile = Some(profile);
63
64        if let Some(ref cloud_config) = profile.cloud {
65            if let Some(ref tenant_id) = cloud_config.tenant_id {
66                self.tenant = Self::resolve_tenant(tenant_id)?;
67            }
68        }
69
70        Ok(self)
71    }
72
73    fn resolve_tenant(tenant_id: &str) -> CloudResult<Option<ResolvedTenant>> {
74        let cloud_paths = get_cloud_paths().map_err(|_| CloudError::TenantsNotSynced)?;
75        let tenants_path = cloud_paths.resolve(CloudPath::Tenants);
76
77        if !tenants_path.exists() {
78            return Ok(None);
79        }
80
81        let store =
82            TenantStore::load_from_path(&tenants_path).map_err(|_| CloudError::TenantsNotSynced)?;
83
84        store.find_tenant(tenant_id).map_or_else(
85            || {
86                Err(CloudError::TenantNotFound {
87                    tenant_id: tenant_id.to_string(),
88                })
89            },
90            |tenant| Ok(Some(ResolvedTenant::from(tenant.clone()))),
91        )
92    }
93
94    pub fn tenant_id(&self) -> CloudResult<&str> {
95        self.tenant
96            .as_ref()
97            .map(|t| t.id.as_str())
98            .ok_or(CloudError::TenantNotConfigured)
99    }
100
101    pub fn app_id(&self) -> CloudResult<&str> {
102        self.tenant
103            .as_ref()
104            .and_then(|t| t.app_id.as_deref())
105            .ok_or(CloudError::AppNotConfigured)
106    }
107
108    #[must_use]
109    pub fn tenant_name(&self) -> &str {
110        self.tenant.as_ref().map_or("unknown", |t| t.name.as_str())
111    }
112
113    #[must_use]
114    pub fn hostname(&self) -> Option<&str> {
115        self.tenant.as_ref().and_then(|t| t.hostname.as_deref())
116    }
117
118    pub fn profile(&self) -> CloudResult<&'static Profile> {
119        self.profile.ok_or_else(|| CloudError::ProfileRequired {
120            message: "Profile not loaded in context".into(),
121        })
122    }
123
124    #[must_use]
125    pub const fn has_tenant(&self) -> bool {
126        self.tenant.is_some()
127    }
128}