ai_tokenopt 0.5.10

Adaptive token optimization engine for LLM inference pipelines — compresses prompts, conversation history, tool schemas, and output streams to minimize token usage while preserving response quality.
Documentation
//! Runtime prompt template loader.
//!
//! Provides a two-level lookup for named prompt templates:
//!
//! 1. **Filesystem override** — when `prompt_template_dir` is configured, the
//!    loader attempts to read `{dir}/{name}.prompt.txt`.  A missing file is
//!    treated as "not overridden" (not an error), allowing partial overrides.
//!
//! 2. **Compiled-in fallback** — templates baked into the binary via `build.rs`
//!    are checked next. These are always available and require no external files.
//!
//! # Example
//!
//! ```rust
//! use ai_tokenopt::prompt::template_loader::TemplateLoader;
//! use std::path::PathBuf;
//!
//! // Fallback-only loader (no filesystem directory configured)
//! let loader = TemplateLoader::new(None::<PathBuf>);
//! // Returns None when the template is not found in compiled-in defaults
//! let _ = loader.load("my_template");
//! ```

use std::path::PathBuf;

/// Loads prompt templates from an optional filesystem directory, falling back
/// to the compiled-in [`YAML_PROMPTS`](crate::YAML_PROMPTS) constants.
#[derive(Debug, Clone)]
pub struct TemplateLoader {
    prompt_dir: Option<PathBuf>,
}

impl TemplateLoader {
    /// Create a new loader.
    ///
    /// `prompt_dir` is the optional directory path from
    /// [`TokenOptimizationConfig::prompt_template_dir`](crate::TokenOptimizationConfig::prompt_template_dir).
    /// Pass `None` to use compiled-in templates only.
    #[must_use]
    pub fn new(prompt_dir: Option<impl Into<PathBuf>>) -> Self {
        Self {
            prompt_dir: prompt_dir.map(Into::into),
        }
    }

    /// Load a named template, returning its content as an owned `String`.
    ///
    /// Lookup order:
    /// 1. `{prompt_template_dir}/{name}.prompt.txt` (filesystem, if configured)
    /// 2. Compiled-in [`YAML_PROMPTS`](crate::YAML_PROMPTS) constant
    ///
    /// Returns `None` when the template is not found in either location.
    #[must_use]
    pub fn load(&self, name: &str) -> Option<String> {
        // 1. Try filesystem override
        if let Some(ref dir) = self.prompt_dir {
            let path = dir.join(format!("{name}.prompt.txt"));
            match std::fs::read_to_string(&path) {
                Ok(content) => {
                    tracing::debug!(name, path = %path.display(), "Loaded prompt template from filesystem");
                    return Some(content);
                },
                Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
                    // Not an error — just fall through to compiled-in default
                },
                Err(e) => {
                    tracing::warn!(name, path = %path.display(), error = %e, "Failed to read prompt template file, using compiled-in default");
                },
            }
        }

        // 2. Compiled-in fallback
        crate::YAML_PROMPTS
            .iter()
            .find(|(n, _)| *n == name)
            .map(|(_, content)| (*content).to_owned())
    }

    /// Check whether a filesystem override directory is configured.
    #[must_use]
    pub fn has_override_dir(&self) -> bool {
        self.prompt_dir.is_some()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn loader_without_dir_returns_compiled_in_template() {
        let loader = TemplateLoader::new(None::<PathBuf>);
        // ai_tokenopt/prompts/ may be empty in test environments — just check
        // that the API works and returns None gracefully for unknown names.
        let result = loader.load("__nonexistent_template_42__");
        assert!(result.is_none(), "unknown template should return None");
    }

    #[test]
    fn loader_without_dir_has_no_override_dir() {
        let loader = TemplateLoader::new(None::<PathBuf>);
        assert!(!loader.has_override_dir());
    }

    #[test]
    fn loader_with_dir_has_override_dir() {
        let loader = TemplateLoader::new(Some("/some/dir"));
        assert!(loader.has_override_dir());
    }

    #[test]
    fn loader_with_nonexistent_dir_falls_back_to_compiled_in() {
        // A directory that doesn't exist should gracefully fall through to
        // compiled-in defaults without panicking.
        let loader = TemplateLoader::new(Some("/tmp/nonexistent_pisovereign_prompts_dir_xyz987"));
        let result = loader.load("__nonexistent_template_42__");
        assert!(result.is_none());
    }

    #[test]
    fn loader_reads_filesystem_override_when_file_exists() {
        let dir = std::env::temp_dir().join("ai_tokenopt_template_loader_test");
        std::fs::create_dir_all(&dir).expect("create tmp dir");
        let file_path = dir.join("my_prompt.prompt.txt");
        std::fs::write(&file_path, "custom content").expect("write file");

        let loader = TemplateLoader::new(Some(&dir));
        let result = loader.load("my_prompt");
        std::fs::remove_file(&file_path).ok();
        std::fs::remove_dir(&dir).ok();

        assert_eq!(result.as_deref(), Some("custom content"));
    }

    #[test]
    fn filesystem_override_takes_priority_over_compiled_in() {
        // If a compiled-in template matches AND a file override exists, the file wins.
        // We can only test this if YAML_PROMPTS is non-empty; otherwise just verify
        // filesystem priority holds when the name is known.
        let dir = std::env::temp_dir().join("ai_tokenopt_template_override_test");
        std::fs::create_dir_all(&dir).expect("create tmp dir");

        // Plant an override for any compiled-in template name, or a fresh synthetic name
        let name = crate::YAML_PROMPTS
            .first()
            .map_or("test_override", |(n, _)| *n);
        let file_path = dir.join(format!("{name}.prompt.txt"));
        std::fs::write(&file_path, "filesystem_override_content").expect("write file");

        let loader = TemplateLoader::new(Some(&dir));
        let result = loader.load(name);
        std::fs::remove_file(&file_path).ok();
        std::fs::remove_dir(&dir).ok();

        assert_eq!(
            result.as_deref(),
            Some("filesystem_override_content"),
            "filesystem override must take priority over compiled-in template"
        );
    }
}