Skip to main content

systemprompt_config/paths/
build.rs

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