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 serde::{Deserialize, Serialize};
18
19#[cfg(feature = "cli-integration")]
20use crate::common::Provider;
21use crate::common::{BaseRegistry, Named, RegistryItem, SourceType};
22
23pub use crate::common::SourceType as OutputStyleSourceType;
24
25/// Definition of an output style.
26///
27/// Output styles customize Claude's behavior by modifying the system prompt.
28/// The `keep_coding_instructions` flag determines whether standard coding
29/// instructions are retained (true) or replaced by the custom prompt (false).
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct OutputStyle {
32    pub name: String,
33    pub description: String,
34    pub prompt: String,
35    #[serde(default, alias = "source")]
36    pub source_type: OutputStyleSourceType,
37    #[serde(default, rename = "keep-coding-instructions")]
38    pub keep_coding_instructions: bool,
39}
40
41impl OutputStyle {
42    pub fn new(
43        name: impl Into<String>,
44        description: impl Into<String>,
45        prompt: impl Into<String>,
46    ) -> Self {
47        Self {
48            name: name.into(),
49            description: description.into(),
50            prompt: prompt.into(),
51            source_type: OutputStyleSourceType::default(),
52            keep_coding_instructions: false,
53        }
54    }
55
56    pub fn with_source_type(mut self, source_type: OutputStyleSourceType) -> Self {
57        self.source_type = source_type;
58        self
59    }
60
61    pub fn with_keep_coding_instructions(mut self, keep: bool) -> Self {
62        self.keep_coding_instructions = keep;
63        self
64    }
65
66    pub fn is_default(&self) -> bool {
67        self.name == "default" && self.prompt.is_empty()
68    }
69}
70
71impl Named for OutputStyle {
72    fn name(&self) -> &str {
73        &self.name
74    }
75}
76
77impl RegistryItem for OutputStyle {
78    fn source_type(&self) -> SourceType {
79        self.source_type
80    }
81}
82
83#[cfg(feature = "cli-integration")]
84pub type OutputStyleRegistry = BaseRegistry<OutputStyle, OutputStyleLoader>;
85
86#[cfg(feature = "cli-integration")]
87impl OutputStyleRegistry {
88    pub fn with_builtins() -> Self {
89        let mut registry = Self::new();
90        registry.register_all(builtin_styles());
91        registry
92    }
93
94    pub async fn load_from_directories(
95        &mut self,
96        working_dir: Option<&std::path::Path>,
97    ) -> crate::Result<()> {
98        let builtins = InMemoryOutputStyleProvider::new()
99            .with_items(builtin_styles())
100            .with_priority(0)
101            .with_source_type(SourceType::Builtin);
102
103        let mut chain = ChainOutputStyleProvider::new().with(builtins);
104
105        if let Some(dir) = working_dir {
106            let project = file_output_style_provider()
107                .with_project_path(dir)
108                .with_priority(20)
109                .with_source_type(SourceType::Project);
110            chain = chain.with(project);
111        }
112
113        let user = file_output_style_provider()
114            .with_user_path()
115            .with_priority(10)
116            .with_source_type(SourceType::User);
117        let chain = chain.with(user);
118
119        let loaded = chain.load_all().await?;
120        self.register_all(loaded);
121        Ok(())
122    }
123}
124
125impl Default for OutputStyle {
126    fn default() -> Self {
127        default_style()
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_output_style_new() {
137        let style = OutputStyle::new("test", "A test style", "Test prompt");
138
139        assert_eq!(style.name, "test");
140        assert_eq!(style.description, "A test style");
141        assert_eq!(style.prompt, "Test prompt");
142        assert_eq!(style.source_type, OutputStyleSourceType::User);
143        assert!(!style.keep_coding_instructions);
144    }
145
146    #[test]
147    fn test_output_style_builder() {
148        let style = OutputStyle::new("custom", "Custom style", "Custom prompt")
149            .with_source_type(OutputStyleSourceType::Project)
150            .with_keep_coding_instructions(true);
151
152        assert_eq!(style.source_type, OutputStyleSourceType::Project);
153        assert!(style.keep_coding_instructions);
154    }
155
156    #[test]
157    fn test_default_style() {
158        let style = default_style();
159
160        assert!(style.is_default());
161        assert_eq!(style.name, "default");
162        assert!(style.keep_coding_instructions);
163    }
164
165    #[test]
166    fn test_source_type_display() {
167        assert_eq!(OutputStyleSourceType::Builtin.to_string(), "builtin");
168        assert_eq!(OutputStyleSourceType::User.to_string(), "user");
169        assert_eq!(OutputStyleSourceType::Project.to_string(), "project");
170        assert_eq!(OutputStyleSourceType::Managed.to_string(), "managed");
171    }
172}