codex-home/src/instructions/mod.rs
77 lines
use std::io;use codex_extension_api::LoadUserInstructionsFuture;use codex_extension_api::LoadedUserInstructions;use codex_extension_api::UserInstructions;use codex_extension_api::UserInstructionsProvider;use codex_utils_absolute_path::AbsolutePathBuf;const DEFAULT_AGENTS_MD_FILENAME: &str = "AGENTS.md";const LOCAL_AGENTS_MD_FILENAME: &str = "AGENTS.override.md";/// Loads user instructions from a Codex home directory.#[derive(Clone, Debug)]pub struct CodexHomeUserInstructionsProvider { codex_home: AbsolutePathBuf,}impl CodexHomeUserInstructionsProvider { /// Creates a provider rooted at the supplied absolute Codex home directory. pub fn new(codex_home: AbsolutePathBuf) -> Self { Self { codex_home } } async fn load_from_codex_home(&self) -> LoadedUserInstructions { let mut warnings = Vec::new(); for candidate in [LOCAL_AGENTS_MD_FILENAME, DEFAULT_AGENTS_MD_FILENAME] { let path = self.codex_home.join(candidate); match tokio::fs::metadata(path.as_path()).await { Ok(metadata) if !metadata.is_file() => continue, Ok(_) => {} Err(err) if err.kind() == io::ErrorKind::NotFound => continue, Err(err) => { warnings.push(format!( "Failed to read global AGENTS.md instructions from `{}`: {err}", path.display() )); continue; } } let data = match tokio::fs::read(path.as_path()).await { Ok(data) => data, Err(err) if err.kind() == io::ErrorKind::NotFound => continue, Err(err) => { warnings.push(format!( "Failed to read global AGENTS.md instructions from `{}`: {err}", path.display() )); continue; } }; let contents = String::from_utf8_lossy(&data); let trimmed = contents.trim(); if !trimmed.is_empty() { return LoadedUserInstructions { instructions: Some(UserInstructions { text: trimmed.to_string(), source: path, }), warnings, }; } } LoadedUserInstructions { instructions: None, warnings, } }}impl UserInstructionsProvider for CodexHomeUserInstructionsProvider { fn load_user_instructions(&self) -> LoadUserInstructionsFuture<'_> { Box::pin(self.load_from_codex_home()) }}#[cfg(test)]mod tests;