claude_agent/output_style/
mod.rs

1mod builtin;
2#[cfg(feature = "cli-integration")]
3mod generator;
4#[cfg(feature = "cli-integration")]
5mod loader;
6mod provider;
7
8pub use builtin::{builtin_styles, default_style, explanatory_style, find_builtin, learning_style};
9#[cfg(feature = "cli-integration")]
10pub use generator::SystemPromptGenerator;
11#[cfg(feature = "cli-integration")]
12pub use loader::{OutputStyleFrontmatter, OutputStyleLoader};
13pub use provider::InMemoryOutputStyleProvider;
14#[cfg(feature = "cli-integration")]
15pub use provider::{ChainOutputStyleProvider, FileOutputStyleProvider, file_output_style_provider};
16
17use async_trait::async_trait;
18use serde::{Deserialize, Serialize};
19
20#[cfg(feature = "cli-integration")]
21use crate::common::Provider;
22use crate::common::{ContentSource, Index, IndexRegistry, Named, SourceType};
23
24/// Definition of an output style.
25///
26/// Output styles customize Claude's behavior by modifying the system prompt.
27/// The `keep_coding_instructions` flag determines whether standard coding
28/// instructions are retained (true) or replaced by the custom prompt (false).
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct OutputStyle {
31    pub name: String,
32    pub description: String,
33    /// The prompt content for this style.
34    pub prompt: String,
35    /// Content source for lazy loading (optional, defaults to InMemory from prompt).
36    #[serde(default)]
37    pub source: ContentSource,
38    #[serde(default)]
39    pub source_type: SourceType,
40    #[serde(default, rename = "keep-coding-instructions")]
41    pub keep_coding_instructions: bool,
42}
43
44impl OutputStyle {
45    /// Create a new output style with the given name, description, and prompt.
46    ///
47    /// By default, `keep_coding_instructions` is `true` to match the behavior
48    /// of the default style and `CompactStrategy::default()`.
49    pub fn new(
50        name: impl Into<String>,
51        description: impl Into<String>,
52        prompt: impl Into<String>,
53    ) -> Self {
54        let prompt_str = prompt.into();
55        Self {
56            name: name.into(),
57            description: description.into(),
58            source: ContentSource::in_memory(&prompt_str),
59            prompt: prompt_str,
60            source_type: SourceType::default(),
61            keep_coding_instructions: true,
62        }
63    }
64
65    pub fn with_source_type(mut self, source_type: SourceType) -> Self {
66        self.source_type = source_type;
67        self
68    }
69
70    pub fn with_keep_coding_instructions(mut self, keep: bool) -> Self {
71        self.keep_coding_instructions = keep;
72        self
73    }
74
75    pub fn is_default(&self) -> bool {
76        self.name == "default" && self.prompt.is_empty()
77    }
78}
79
80impl Named for OutputStyle {
81    fn name(&self) -> &str {
82        &self.name
83    }
84}
85
86#[async_trait]
87impl Index for OutputStyle {
88    fn source(&self) -> &ContentSource {
89        &self.source
90    }
91
92    fn source_type(&self) -> SourceType {
93        self.source_type
94    }
95
96    fn to_summary_line(&self) -> String {
97        format!("- {}: {}", self.name, self.description)
98    }
99
100    fn description(&self) -> &str {
101        &self.description
102    }
103}
104
105/// Registry for output styles.
106#[cfg(feature = "cli-integration")]
107pub type OutputStyleRegistry = IndexRegistry<OutputStyle>;
108
109#[cfg(feature = "cli-integration")]
110impl OutputStyleRegistry {
111    pub fn with_builtins() -> Self {
112        let mut registry = Self::new();
113        registry.register_all(builtin_styles());
114        registry
115    }
116
117    pub async fn load_from_directories(
118        &mut self,
119        working_dir: Option<&std::path::Path>,
120    ) -> crate::Result<()> {
121        let builtins = InMemoryOutputStyleProvider::new()
122            .with_items(builtin_styles())
123            .with_priority(0)
124            .with_source_type(SourceType::Builtin);
125
126        let mut chain = ChainOutputStyleProvider::new().with(builtins);
127
128        if let Some(dir) = working_dir {
129            let project = file_output_style_provider()
130                .with_project_path(dir)
131                .with_priority(20)
132                .with_source_type(SourceType::Project);
133            chain = chain.with(project);
134        }
135
136        let user = file_output_style_provider()
137            .with_user_path()
138            .with_priority(10)
139            .with_source_type(SourceType::User);
140        let chain = chain.with(user);
141
142        let loaded = chain.load_all().await?;
143        self.register_all(loaded);
144        Ok(())
145    }
146}
147
148impl Default for OutputStyle {
149    fn default() -> Self {
150        default_style()
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_output_style_new() {
160        let style = OutputStyle::new("test", "A test style", "Test prompt");
161
162        assert_eq!(style.name, "test");
163        assert_eq!(style.description, "A test style");
164        assert_eq!(style.prompt, "Test prompt");
165        assert_eq!(style.source_type, SourceType::User);
166        // Default is now true for consistency with CompactStrategy
167        assert!(style.keep_coding_instructions);
168    }
169
170    #[test]
171    fn test_output_style_builder() {
172        let style = OutputStyle::new("custom", "Custom style", "Custom prompt")
173            .with_source_type(SourceType::Project)
174            .with_keep_coding_instructions(true);
175
176        assert_eq!(style.source_type, SourceType::Project);
177        assert!(style.keep_coding_instructions);
178    }
179
180    #[test]
181    fn test_default_style() {
182        let style = default_style();
183
184        assert!(style.is_default());
185        assert_eq!(style.name, "default");
186        assert!(style.keep_coding_instructions);
187    }
188
189    #[test]
190    fn test_source_type_display() {
191        assert_eq!(SourceType::Builtin.to_string(), "builtin");
192        assert_eq!(SourceType::User.to_string(), "user");
193        assert_eq!(SourceType::Project.to_string(), "project");
194        assert_eq!(SourceType::Managed.to_string(), "managed");
195    }
196}