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}