Skip to main content

yapi_core/
config.rs

1//! Where yapi keeps its state. Same layout as pi, under yapi's own directories.
2
3use std::path::{Path, PathBuf};
4use std::sync::atomic::{AtomicBool, Ordering};
5
6use crate::tools::path::{expand, home_dir};
7
8/// Application name in messages and paths.
9pub const APP_NAME: &str = "yapi";
10/// Project configuration directory name.
11pub const PROJECT_DIR: &str = ".yapi";
12/// Environment variable overriding the agent directory.
13pub const AGENT_DIR_ENV: &str = "YAPI_CODING_AGENT_DIR";
14/// Environment variable overriding the session directory. `--session-dir` wins.
15pub const SESSION_DIR_ENV: &str = "YAPI_CODING_AGENT_SESSION_DIR";
16
17/// Set once offline mode is on for the process.
18static OFFLINE: AtomicBool = AtomicBool::new(false);
19
20/// The variables pi sets on its own process in offline mode.
21const OFFLINE_ENV: [&str; 2] = ["PI_OFFLINE", "PI_SKIP_VERSION_CHECK"];
22
23/// Turns on offline mode for the process, as pi does for `--offline` or a
24/// true `PI_OFFLINE`: from then on `PI_OFFLINE` and `PI_SKIP_VERSION_CHECK`
25/// read as `1` here and in every child's environment.
26pub fn set_offline() {
27    OFFLINE.store(true, Ordering::Relaxed);
28}
29
30/// Environment variable `name`, with the values offline mode sets.
31pub fn env_var(name: &str) -> Option<String> {
32    if OFFLINE.load(Ordering::Relaxed) && OFFLINE_ENV.contains(&name) {
33        return Some("1".to_owned());
34    }
35    std::env::var(name).ok()
36}
37
38/// Whether environment variable `name` is set and not empty, as pi tests
39/// `process.env.PI_OFFLINE` and `PI_SKIP_VERSION_CHECK`.
40pub fn env_set(name: &str) -> bool {
41    env_var(name).is_some_and(|value| !value.is_empty())
42}
43
44/// Variables every child process gets, as pi sets them on its own process:
45/// the agent markers `PI_CODING_AGENT=true` and `AI_AGENT=yapi`, the offline
46/// variables in offline mode, and the proxy from the `httpProxy` setting
47/// where the environment has none.
48pub fn child_env() -> Vec<(&'static str, String)> {
49    let mut env = vec![
50        ("PI_CODING_AGENT", "true".to_owned()),
51        ("AI_AGENT", APP_NAME.to_owned()),
52    ];
53    if OFFLINE.load(Ordering::Relaxed) {
54        env.extend(OFFLINE_ENV.map(|name| (name, "1".to_owned())));
55    }
56    env.extend(yapi_ai::http::proxy_env());
57    env
58}
59
60/// The agent directory: `YAPI_CODING_AGENT_DIR`, else `~/.yapi/agent`.
61pub fn agent_dir() -> PathBuf {
62    match std::env::var(AGENT_DIR_ENV) {
63        Ok(dir) if !dir.is_empty() => PathBuf::from(expand(&dir)),
64        _ => home_dir().join(PROJECT_DIR).join("agent"),
65    }
66}
67
68/// Where sessions for `cwd` live by default:
69/// `<agent>/sessions/--<cwd with separators as dashes>--`, both resolved
70/// against the process directory.
71pub fn default_session_dir(agent_dir: &Path, cwd: &Path) -> PathBuf {
72    let base = std::env::current_dir().unwrap_or_default();
73    let resolve = |path: &Path| crate::tools::path::resolve_lexically(&base, path);
74    let cwd = resolve(cwd);
75    let text = cwd.to_string_lossy();
76    let trimmed = text.trim_start_matches(['/', '\\']);
77    let safe: String = trimmed
78        .chars()
79        .map(|c| {
80            if matches!(c, '/' | '\\' | ':') {
81                '-'
82            } else {
83                c
84            }
85        })
86        .collect();
87    resolve(agent_dir)
88        .join("sessions")
89        .join(format!("--{safe}--"))
90}
91
92/// Whether environment variable `name` is set to `1`, `true` or `yes`, as pi
93/// reads `PI_OFFLINE`.
94pub fn env_flag(name: &str) -> bool {
95    env_var(name).is_some_and(|value| {
96        value == "1" || matches!(value.to_lowercase().as_str(), "true" | "yes")
97    })
98}
99
100/// The documentation the model reads: yapi's pages, with pi's README, docs
101/// and examples under `pi/`. The installer or yapi's first run puts it here.
102pub fn docs_dir(agent_dir: &Path) -> PathBuf {
103    agent_dir.join("docs")
104}
105
106/// Executables the agent installs for its tools, prepended to `PATH` for commands.
107pub fn bin_dir(agent_dir: &Path) -> PathBuf {
108    agent_dir.join("bin")
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn session_dirs_match_pi() {
117        assert_eq!(
118            default_session_dir(Path::new("/h/.yapi/agent"), Path::new("/home/user/project")),
119            PathBuf::from("/h/.yapi/agent/sessions/--home-user-project--")
120        );
121    }
122}