vtcode_config/core/
skills.rs1use serde::{Deserialize, Serialize};
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
25#[serde(rename_all = "kebab-case")]
26pub struct BundledSkillsConfig {
27 #[serde(default = "default_bundled_skills_enabled")]
29 pub enabled: bool,
30}
31
32impl Default for BundledSkillsConfig {
33 fn default() -> Self {
34 Self {
35 enabled: default_bundled_skills_enabled(),
36 }
37 }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
43#[serde(rename_all = "kebab-case")]
44pub struct SkillsConfig {
45 #[serde(default)]
47 pub bundled: BundledSkillsConfig,
48
49 #[serde(default = "default_render_mode")]
53 pub render_mode: SkillsRenderMode,
54
55 #[serde(default = "default_prompt_format")]
59 pub prompt_format: PromptFormat,
60
61 #[serde(default = "default_max_skills_in_prompt")]
63 pub max_skills_in_prompt: usize,
64
65 #[serde(default = "default_enable_auto_trigger")]
67 pub enable_auto_trigger: bool,
68
69 #[serde(default = "default_enable_description_matching")]
71 pub enable_description_matching: bool,
72
73 #[serde(default = "default_min_keyword_matches")]
75 pub min_keyword_matches: usize,
76}
77
78impl Default for SkillsConfig {
79 fn default() -> Self {
80 Self {
81 bundled: BundledSkillsConfig::default(),
82 render_mode: default_render_mode(),
83 prompt_format: default_prompt_format(),
84 max_skills_in_prompt: default_max_skills_in_prompt(),
85 enable_auto_trigger: default_enable_auto_trigger(),
86 enable_description_matching: default_enable_description_matching(),
87 min_keyword_matches: default_min_keyword_matches(),
88 }
89 }
90}
91
92#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
94#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
95#[serde(rename_all = "lowercase")]
96pub enum SkillsRenderMode {
97 #[default]
99 Lean,
100 Full,
102}
103
104#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
106#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
107#[serde(rename_all = "lowercase")]
108pub enum PromptFormat {
109 #[default]
111 Xml,
112 Markdown,
114}
115
116fn default_render_mode() -> SkillsRenderMode {
117 SkillsRenderMode::Lean
118}
119
120fn default_bundled_skills_enabled() -> bool {
121 true
122}
123
124fn default_prompt_format() -> PromptFormat {
125 PromptFormat::Xml
126}
127
128fn default_max_skills_in_prompt() -> usize {
129 10
130}
131
132fn default_enable_auto_trigger() -> bool {
133 true
134}
135
136fn default_enable_description_matching() -> bool {
137 true
138}
139
140fn default_min_keyword_matches() -> usize {
141 2
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_default_skills_config() {
150 let config = SkillsConfig::default();
151 assert!(config.bundled.enabled);
152 assert_eq!(config.render_mode, SkillsRenderMode::Lean);
153 assert_eq!(config.prompt_format, PromptFormat::Xml);
154 assert_eq!(config.max_skills_in_prompt, 10);
155 assert!(config.enable_auto_trigger);
156 assert!(config.enable_description_matching);
157 assert_eq!(config.min_keyword_matches, 2);
158 }
159
160 #[test]
161 fn test_skills_render_mode_serde() {
162 let lean = SkillsRenderMode::Lean;
164 let lean_json = serde_json::to_string(&lean).unwrap();
165 assert_eq!(lean_json, r#""lean""#);
166
167 let full = SkillsRenderMode::Full;
168 let full_json = serde_json::to_string(&full).unwrap();
169 assert_eq!(full_json, r#""full""#);
170
171 let lean_de: SkillsRenderMode = serde_json::from_str(r#""lean""#).unwrap();
173 assert_eq!(lean_de, SkillsRenderMode::Lean);
174
175 let full_de: SkillsRenderMode = serde_json::from_str(r#""full""#).unwrap();
176 assert_eq!(full_de, SkillsRenderMode::Full);
177 }
178
179 #[test]
180 fn test_prompt_format_serde() {
181 let xml = PromptFormat::Xml;
183 let xml_json = serde_json::to_string(&xml).unwrap();
184 assert_eq!(xml_json, r#""xml""#);
185
186 let markdown = PromptFormat::Markdown;
187 let markdown_json = serde_json::to_string(&markdown).unwrap();
188 assert_eq!(markdown_json, r#""markdown""#);
189
190 let xml_de: PromptFormat = serde_json::from_str(r#""xml""#).unwrap();
192 assert_eq!(xml_de, PromptFormat::Xml);
193
194 let markdown_de: PromptFormat = serde_json::from_str(r#""markdown""#).unwrap();
195 assert_eq!(markdown_de, PromptFormat::Markdown);
196 }
197
198 #[test]
199 fn test_skills_config_serde() {
200 let config = SkillsConfig {
201 bundled: BundledSkillsConfig { enabled: false },
202 render_mode: SkillsRenderMode::Full,
203 prompt_format: PromptFormat::Markdown,
204 max_skills_in_prompt: 15,
205 enable_auto_trigger: false,
206 enable_description_matching: false,
207 min_keyword_matches: 3,
208 };
209
210 let json = serde_json::to_string_pretty(&config).unwrap();
211 let deserialized: SkillsConfig = serde_json::from_str(&json).unwrap();
212 assert_eq!(config, deserialized);
213 }
214
215 #[test]
216 fn test_skills_config_toml_parses_bundled_settings() {
217 let config: SkillsConfig = toml::from_str(
218 r#"
219 render-mode = "full"
220 prompt-format = "markdown"
221 max-skills-in-prompt = 15
222 enable-auto-trigger = false
223 enable-description-matching = false
224 min-keyword-matches = 3
225
226 [bundled]
227 enabled = false
228 "#,
229 )
230 .unwrap();
231
232 assert!(!config.bundled.enabled);
233 assert_eq!(config.render_mode, SkillsRenderMode::Full);
234 assert_eq!(config.prompt_format, PromptFormat::Markdown);
235 assert_eq!(config.max_skills_in_prompt, 15);
236 assert!(!config.enable_auto_trigger);
237 assert!(!config.enable_description_matching);
238 assert_eq!(config.min_keyword_matches, 3);
239 }
240}