Skip to main content

agent_core_runtime/agent/
environment.rs

1//! Environment context for system prompts.
2//!
3//! Gathers working directory and system information to include in
4//! the LLM system prompt, giving the model awareness of its environment.
5
6use std::env;
7use std::path::PathBuf;
8
9use chrono::Local;
10
11/// Environment context information.
12///
13/// Contains details about the current working environment that can be
14/// included in the system prompt to give the LLM context awareness.
15#[derive(Debug, Clone)]
16pub struct EnvironmentContext {
17    /// Current working directory.
18    pub working_directory: PathBuf,
19    /// Operating system (e.g., "darwin", "linux", "windows").
20    pub platform: String,
21    /// OS version string (e.g., "Darwin 25.2.0").
22    pub os_version: Option<String>,
23    /// Today's date in YYYY-MM-DD format.
24    pub date: String,
25}
26
27impl EnvironmentContext {
28    /// Gather environment context from the current system.
29    ///
30    /// This collects:
31    /// - Current working directory
32    /// - Platform (OS type)
33    /// - OS version (via uname on Unix, ver on Windows)
34    /// - Current date
35    pub fn gather() -> Self {
36        let working_directory = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
37        let platform = env::consts::OS.to_string();
38        let os_version = Self::get_os_version();
39        let date = Local::now().format("%Y-%m-%d").to_string();
40
41        Self {
42            working_directory,
43            platform,
44            os_version,
45            date,
46        }
47    }
48
49    /// Get the OS version string.
50    #[cfg(unix)]
51    fn get_os_version() -> Option<String> {
52        use std::process::Command;
53
54        let output = Command::new("uname").arg("-rs").output().ok()?;
55
56        if output.status.success() {
57            String::from_utf8(output.stdout)
58                .ok()
59                .map(|s| s.trim().to_string())
60        } else {
61            None
62        }
63    }
64
65    /// Get the OS version string on Windows.
66    #[cfg(windows)]
67    fn get_os_version() -> Option<String> {
68        use std::process::Command;
69
70        let output = Command::new("cmd").args(["/C", "ver"]).output().ok()?;
71
72        if output.status.success() {
73            String::from_utf8(output.stdout)
74                .ok()
75                .map(|s| s.trim().to_string())
76        } else {
77            None
78        }
79    }
80
81    /// Fallback for other platforms.
82    #[cfg(not(any(unix, windows)))]
83    fn get_os_version() -> Option<String> {
84        None
85    }
86
87    /// Format the environment context as a system prompt section.
88    ///
89    /// Returns a string wrapped in `<env>` tags suitable for appending
90    /// to a system prompt.
91    pub fn to_prompt_section(&self) -> String {
92        let mut lines = Vec::new();
93
94        lines.push(format!(
95            "Working directory: {}",
96            self.working_directory.display()
97        ));
98        lines.push(format!("Platform: {}", self.platform));
99
100        if let Some(ref version) = self.os_version {
101            lines.push(format!("OS Version: {}", version));
102        }
103
104        lines.push(format!("Today's date: {}", self.date));
105
106        format!("<env>\n{}\n</env>", lines.join("\n"))
107    }
108}
109
110impl Default for EnvironmentContext {
111    fn default() -> Self {
112        Self::gather()
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_gather_environment() {
122        let ctx = EnvironmentContext::gather();
123
124        // Should have a working directory
125        assert!(!ctx.working_directory.as_os_str().is_empty());
126
127        // Should have a platform
128        assert!(!ctx.platform.is_empty());
129
130        // Date should be in YYYY-MM-DD format
131        assert_eq!(ctx.date.len(), 10);
132        assert!(ctx.date.contains('-'));
133    }
134
135    #[test]
136    fn test_to_prompt_section() {
137        let ctx = EnvironmentContext {
138            working_directory: PathBuf::from("/test/path"),
139            platform: "darwin".to_string(),
140            os_version: Some("Darwin 25.2.0".to_string()),
141            date: "2026-01-31".to_string(),
142        };
143
144        let section = ctx.to_prompt_section();
145
146        assert!(section.starts_with("<env>"));
147        assert!(section.ends_with("</env>"));
148        assert!(section.contains("Working directory: /test/path"));
149        assert!(section.contains("Platform: darwin"));
150        assert!(section.contains("OS Version: Darwin 25.2.0"));
151        assert!(section.contains("Today's date: 2026-01-31"));
152    }
153
154    #[test]
155    fn test_to_prompt_section_without_os_version() {
156        let ctx = EnvironmentContext {
157            working_directory: PathBuf::from("/test/path"),
158            platform: "unknown".to_string(),
159            os_version: None,
160            date: "2026-01-31".to_string(),
161        };
162
163        let section = ctx.to_prompt_section();
164
165        assert!(!section.contains("OS Version:"));
166        assert!(section.contains("Platform: unknown"));
167    }
168}