Skip to main content

systemprompt_cloud/paths/
context.rs

1//! [`UnifiedContext`]: resolves credential, tenant, and session file locations
2//! from a discovered project, falling back to configured cloud paths.
3//!
4//! Precedence is explicit cloud paths first, then the discovered project, then
5//! a `.systemprompt`-relative default, so the same accessor works whether
6//! running in a local checkout or a deployed container.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use std::path::{Path, PathBuf};
12
13use super::{CloudPath, CloudPaths, DiscoveredProject};
14use crate::constants::{dir_names, file_names};
15
16#[derive(Debug, Clone)]
17pub struct UnifiedContext {
18    project: Option<DiscoveredProject>,
19    cloud_paths: Option<CloudPaths>,
20}
21
22impl UnifiedContext {
23    #[must_use]
24    pub fn discover() -> Self {
25        let project = DiscoveredProject::discover();
26        Self {
27            project,
28            cloud_paths: None,
29        }
30    }
31
32    #[must_use]
33    pub fn discover_from(start: &Path) -> Self {
34        let project = DiscoveredProject::discover_from(start);
35        Self {
36            project,
37            cloud_paths: None,
38        }
39    }
40
41    pub fn with_profile_paths(
42        mut self,
43        profile_dir: &Path,
44        credentials_path: &str,
45        tenants_path: &str,
46    ) -> Self {
47        self.cloud_paths = Some(CloudPaths::from_config(
48            profile_dir,
49            credentials_path,
50            tenants_path,
51        ));
52        self
53    }
54
55    #[must_use]
56    pub const fn has_project(&self) -> bool {
57        self.project.is_some()
58    }
59
60    #[must_use]
61    pub fn project_root(&self) -> Option<&Path> {
62        self.project.as_ref().map(DiscoveredProject::root)
63    }
64
65    #[must_use]
66    pub fn systemprompt_dir(&self) -> Option<PathBuf> {
67        self.project
68            .as_ref()
69            .map(DiscoveredProject::systemprompt_dir)
70            .map(Path::to_path_buf)
71    }
72
73    #[must_use]
74    pub fn credentials_path(&self) -> PathBuf {
75        if let Some(cloud) = &self.cloud_paths {
76            return cloud.resolve(CloudPath::Credentials);
77        }
78        if let Some(project) = &self.project {
79            return project.credentials_path();
80        }
81        PathBuf::from(dir_names::SYSTEMPROMPT).join(file_names::CREDENTIALS)
82    }
83
84    #[must_use]
85    pub fn tenants_path(&self) -> PathBuf {
86        if let Some(cloud) = &self.cloud_paths {
87            return cloud.resolve(CloudPath::Tenants);
88        }
89        if let Some(project) = &self.project {
90            return project.tenants_path();
91        }
92        PathBuf::from(dir_names::SYSTEMPROMPT).join(file_names::TENANTS)
93    }
94
95    #[must_use]
96    pub fn session_path(&self) -> PathBuf {
97        if let Some(cloud) = &self.cloud_paths {
98            return cloud.resolve(CloudPath::CliSession);
99        }
100        if let Some(project) = &self.project {
101            return project.session_path();
102        }
103        PathBuf::from(dir_names::SYSTEMPROMPT).join(file_names::SESSION)
104    }
105
106    #[must_use]
107    pub fn profiles_dir(&self) -> Option<PathBuf> {
108        self.project.as_ref().map(DiscoveredProject::profiles_dir)
109    }
110
111    #[must_use]
112    pub fn profile_dir(&self, name: &str) -> Option<PathBuf> {
113        self.project.as_ref().map(|p| p.profile_dir(name))
114    }
115
116    #[must_use]
117    pub fn docker_dir(&self) -> Option<PathBuf> {
118        self.project.as_ref().map(DiscoveredProject::docker_dir)
119    }
120
121    #[must_use]
122    pub fn storage_dir(&self) -> Option<PathBuf> {
123        self.project.as_ref().map(DiscoveredProject::storage_dir)
124    }
125
126    #[must_use]
127    pub fn has_credentials(&self) -> bool {
128        self.credentials_path().exists()
129    }
130
131    #[must_use]
132    pub fn has_tenants(&self) -> bool {
133        self.tenants_path().exists()
134    }
135
136    #[must_use]
137    pub fn has_session(&self) -> bool {
138        self.session_path().exists()
139    }
140
141    #[must_use]
142    pub fn has_profile(&self, name: &str) -> bool {
143        self.project.as_ref().is_some_and(|p| p.has_profile(name))
144    }
145
146    #[must_use]
147    pub const fn project(&self) -> Option<&DiscoveredProject> {
148        self.project.as_ref()
149    }
150
151    #[must_use]
152    pub const fn cloud_paths(&self) -> Option<&CloudPaths> {
153        self.cloud_paths.as_ref()
154    }
155}
156
157impl Default for UnifiedContext {
158    fn default() -> Self {
159        Self::discover()
160    }
161}