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 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#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct OutputStyle {
31 pub name: String,
32 pub description: String,
33 pub prompt: String,
35 #[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 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#[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 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}