claude_agent/output_style/
mod.rs1mod 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#[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}