Skip to main content

systemprompt_models/paths/
build.rs

1use std::path::{Path, PathBuf};
2
3use super::PathError;
4use crate::profile::PathsConfig;
5
6#[derive(Debug, Clone)]
7pub struct BuildPaths {
8    bin: PathBuf,
9}
10
11impl BuildPaths {
12    pub fn from_profile(paths: &PathsConfig) -> Self {
13        Self {
14            bin: PathBuf::from(&paths.bin),
15        }
16    }
17
18    pub fn resolve_binary(&self, name: &str) -> Result<PathBuf, PathError> {
19        let mut searched = Vec::new();
20
21        let exe_name = format!("{}{}", name, std::env::consts::EXE_SUFFIX);
22        let primary = self.bin.join(&exe_name);
23        searched.push(primary.clone());
24
25        let sibling = self.sibling_bin_path();
26        let alt = sibling.as_ref().map(|s| s.join(&exe_name));
27        if let Some(ref alt_path) = alt {
28            searched.push(alt_path.clone());
29        }
30
31        let alt_exists = alt.as_ref().filter(|p| p.exists());
32
33        match (primary.exists(), alt_exists) {
34            (true, Some(alt_path)) => {
35                let primary_mtime = std::fs::metadata(&primary).and_then(|m| m.modified()).ok();
36                let alt_mtime = std::fs::metadata(alt_path).and_then(|m| m.modified()).ok();
37                match (primary_mtime, alt_mtime) {
38                    (Some(p), Some(a)) if a > p => Self::ensure_absolute(alt_path.clone()),
39                    _ => Self::ensure_absolute(primary),
40                }
41            },
42            (true, None) => Self::ensure_absolute(primary),
43            (false, Some(alt_path)) => Self::ensure_absolute(alt_path.clone()),
44            (false, None) => {
45                if !std::env::consts::EXE_SUFFIX.is_empty() {
46                    let path = self.bin.join(name);
47                    searched.push(path.clone());
48                    if path.exists() {
49                        return Self::ensure_absolute(path);
50                    }
51                }
52                Err(PathError::BinaryNotFound {
53                    name: name.to_owned(),
54                    searched,
55                })
56            },
57        }
58    }
59
60    /// Resolve the path of the currently running executable.
61    ///
62    /// Spawning an agent re-execs this same binary with `admin agents run`,
63    /// so the binary to launch is whatever is running now — not a fixed name.
64    /// Deriving it from the OS lets a renamed binary (e.g. `systemprompt-web`)
65    /// spawn its own agents without a hardcoded `systemprompt` lookup.
66    pub fn resolve_self() -> Result<PathBuf, PathError> {
67        let exe = std::env::current_exe()
68            .map_err(|source| PathError::CurrentExeUnavailable { source })?;
69        Self::ensure_absolute(exe)
70    }
71
72    fn sibling_bin_path(&self) -> Option<PathBuf> {
73        let dir_name = self.bin.file_name()?.to_str()?;
74        let sibling_name = match dir_name {
75            "release" => "debug",
76            "debug" => "release",
77            _ => return None,
78        };
79        Some(self.bin.with_file_name(sibling_name))
80    }
81
82    fn ensure_absolute(path: PathBuf) -> Result<PathBuf, PathError> {
83        if path.is_absolute() {
84            Ok(path)
85        } else {
86            std::fs::canonicalize(&path).map_err(|source| PathError::CanonicalizeFailed {
87                path,
88                field: "binary",
89                source,
90            })
91        }
92    }
93
94    pub fn binary_exists(&self, name: &str) -> bool {
95        self.resolve_binary(name).is_ok()
96    }
97
98    pub fn bin(&self) -> &Path {
99        &self.bin
100    }
101}