Skip to main content

claude_dialog/
prompt.rs

1//! System prompt configuration and loading module
2//!
3//! This module handles loading and managing system prompts from files.
4//! It supports both complete prompt replacement and appending to default prompts.
5//!
6//! # Examples
7//!
8//! ```no_run
9//! use claude_dialog::prompt::{SystemPromptConfig, load_system_prompt};
10//!
11//! // Load a single system prompt file
12//! let config = SystemPromptConfig {
13//!     system_prompt_files: vec!["prompt.md".to_string()],
14//!     append_prompt_file: None,
15//! };
16//! let prompt = load_system_prompt(config).unwrap();
17//!
18//! // Load multiple system prompt files
19//! let config = SystemPromptConfig {
20//!     system_prompt_files: vec!["base.md".to_string(), "specific.md".to_string()],
21//!     append_prompt_file: None,
22//! };
23//! let prompt = load_system_prompt(config).unwrap();
24//! ```
25
26use anyhow::{Result, Context};
27use std::fs;
28
29/// Configuration for system prompt loading
30///
31/// This structure determines how system prompts are loaded and combined.
32/// It supports two modes:
33/// 1. Complete replacement with one or more prompt files
34/// 2. Appending additional content to the default prompt
35///
36/// # Examples
37///
38/// ```
39/// use claude_dialog::prompt::SystemPromptConfig;
40///
41/// // Configuration for multiple system prompts
42/// let config = SystemPromptConfig {
43///     system_prompt_files: vec!["base.md".to_string(), "custom.md".to_string()],
44///     append_prompt_file: None,
45/// };
46///
47/// // Configuration for appending to default prompt
48/// let config = SystemPromptConfig {
49///     system_prompt_files: vec![],
50///     append_prompt_file: Some("additions.md".to_string()),
51/// };
52/// ```
53#[derive(Debug)]
54pub struct SystemPromptConfig {
55    /// List of system prompt files to load (replaces default prompt)
56    ///
57    /// When provided, these files completely replace the default system prompt.
58    /// Multiple files are concatenated with double newlines between them.
59    pub system_prompt_files: Vec<String>,
60    
61    /// Optional file to append to the default prompt
62    ///
63    /// When provided (and system_prompt_files is empty), this file's contents
64    /// are appended to the default system prompt rather than replacing it.
65    pub append_prompt_file: Option<String>,
66}
67
68/// Load system prompt based on the provided configuration
69///
70/// This function handles three scenarios:
71/// 1. Multiple system prompt files - loads and concatenates them
72/// 2. Single append file - loads it for appending to default prompt
73/// 3. No files specified - returns empty string
74///
75/// # Arguments
76///
77/// * `config` - Configuration specifying which prompt files to load
78///
79/// # Returns
80///
81/// * `Result<String>` - The loaded prompt content or an error
82///
83/// # Errors
84///
85/// Returns an error if any specified file cannot be read.
86///
87/// # Examples
88///
89/// ```no_run
90/// use claude_dialog::prompt::{SystemPromptConfig, load_system_prompt};
91/// use std::fs;
92///
93/// // Create test files
94/// fs::write("test1.md", "First prompt").unwrap();
95/// fs::write("test2.md", "Second prompt").unwrap();
96///
97/// // Load multiple prompts
98/// let config = SystemPromptConfig {
99///     system_prompt_files: vec!["test1.md".to_string(), "test2.md".to_string()],
100///     append_prompt_file: None,
101/// };
102/// let result = load_system_prompt(config).unwrap();
103/// assert_eq!(result, "First prompt\n\nSecond prompt");
104///
105/// // Clean up
106/// fs::remove_file("test1.md").unwrap();
107/// fs::remove_file("test2.md").unwrap();
108/// ```
109pub fn load_system_prompt(config: SystemPromptConfig) -> Result<String> {
110    if !config.system_prompt_files.is_empty() {
111        // Load and concatenate multiple system prompt files
112        let mut prompts = Vec::new();
113        
114        for file_path in &config.system_prompt_files {
115            let content = fs::read_to_string(file_path)
116                .with_context(|| format!("Failed to read system prompt file: {}", file_path))?;
117            prompts.push(content);
118        }
119        
120        // Join with double newlines between files
121        Ok(prompts.join("\n\n"))
122    } else if let Some(append_file) = config.append_prompt_file {
123        // Load append prompt file
124        let content = fs::read_to_string(&append_file)
125            .with_context(|| format!("Failed to read append prompt file: {}", append_file))?;
126        Ok(content)
127    } else {
128        // No custom prompt
129        Ok(String::new())
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_system_prompt_config() {
139        let config = SystemPromptConfig {
140            system_prompt_files: vec!["test.md".to_string()],
141            append_prompt_file: None,
142        };
143        assert_eq!(config.system_prompt_files.len(), 1);
144        assert!(config.append_prompt_file.is_none());
145    }
146}