use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub struct BundledSkillsConfig {
#[serde(default = "default_bundled_skills_enabled")]
pub enabled: bool,
}
impl Default for BundledSkillsConfig {
fn default() -> Self {
Self {
enabled: default_bundled_skills_enabled(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub struct SkillsConfig {
#[serde(default)]
pub bundled: BundledSkillsConfig,
#[serde(default = "default_render_mode")]
pub render_mode: SkillsRenderMode,
#[serde(default = "default_prompt_format")]
pub prompt_format: PromptFormat,
#[serde(default = "default_max_skills_in_prompt")]
pub max_skills_in_prompt: usize,
#[serde(default = "default_enable_auto_trigger")]
pub enable_auto_trigger: bool,
#[serde(default = "default_enable_description_matching")]
pub enable_description_matching: bool,
#[serde(default = "default_min_keyword_matches")]
pub min_keyword_matches: usize,
}
impl Default for SkillsConfig {
fn default() -> Self {
Self {
bundled: BundledSkillsConfig::default(),
render_mode: default_render_mode(),
prompt_format: default_prompt_format(),
max_skills_in_prompt: default_max_skills_in_prompt(),
enable_auto_trigger: default_enable_auto_trigger(),
enable_description_matching: default_enable_description_matching(),
min_keyword_matches: default_min_keyword_matches(),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "lowercase")]
pub enum SkillsRenderMode {
#[default]
Lean,
Full,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "lowercase")]
pub enum PromptFormat {
#[default]
Xml,
Markdown,
}
fn default_render_mode() -> SkillsRenderMode {
SkillsRenderMode::Lean
}
fn default_bundled_skills_enabled() -> bool {
true
}
fn default_prompt_format() -> PromptFormat {
PromptFormat::Xml
}
fn default_max_skills_in_prompt() -> usize {
10
}
fn default_enable_auto_trigger() -> bool {
true
}
fn default_enable_description_matching() -> bool {
true
}
fn default_min_keyword_matches() -> usize {
2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_skills_config() {
let config = SkillsConfig::default();
assert!(config.bundled.enabled);
assert_eq!(config.render_mode, SkillsRenderMode::Lean);
assert_eq!(config.prompt_format, PromptFormat::Xml);
assert_eq!(config.max_skills_in_prompt, 10);
assert!(config.enable_auto_trigger);
assert!(config.enable_description_matching);
assert_eq!(config.min_keyword_matches, 2);
}
#[test]
fn test_skills_render_mode_serde() {
let lean = SkillsRenderMode::Lean;
let lean_json = serde_json::to_string(&lean).unwrap();
assert_eq!(lean_json, r#""lean""#);
let full = SkillsRenderMode::Full;
let full_json = serde_json::to_string(&full).unwrap();
assert_eq!(full_json, r#""full""#);
let lean_de: SkillsRenderMode = serde_json::from_str(r#""lean""#).unwrap();
assert_eq!(lean_de, SkillsRenderMode::Lean);
let full_de: SkillsRenderMode = serde_json::from_str(r#""full""#).unwrap();
assert_eq!(full_de, SkillsRenderMode::Full);
}
#[test]
fn test_prompt_format_serde() {
let xml = PromptFormat::Xml;
let xml_json = serde_json::to_string(&xml).unwrap();
assert_eq!(xml_json, r#""xml""#);
let markdown = PromptFormat::Markdown;
let markdown_json = serde_json::to_string(&markdown).unwrap();
assert_eq!(markdown_json, r#""markdown""#);
let xml_de: PromptFormat = serde_json::from_str(r#""xml""#).unwrap();
assert_eq!(xml_de, PromptFormat::Xml);
let markdown_de: PromptFormat = serde_json::from_str(r#""markdown""#).unwrap();
assert_eq!(markdown_de, PromptFormat::Markdown);
}
#[test]
fn test_skills_config_serde() {
let config = SkillsConfig {
bundled: BundledSkillsConfig { enabled: false },
render_mode: SkillsRenderMode::Full,
prompt_format: PromptFormat::Markdown,
max_skills_in_prompt: 15,
enable_auto_trigger: false,
enable_description_matching: false,
min_keyword_matches: 3,
};
let json = serde_json::to_string_pretty(&config).unwrap();
let deserialized: SkillsConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config, deserialized);
}
#[test]
fn test_skills_config_toml_parses_bundled_settings() {
let config: SkillsConfig = toml::from_str(
r#"
render-mode = "full"
prompt-format = "markdown"
max-skills-in-prompt = 15
enable-auto-trigger = false
enable-description-matching = false
min-keyword-matches = 3
[bundled]
enabled = false
"#,
)
.unwrap();
assert!(!config.bundled.enabled);
assert_eq!(config.render_mode, SkillsRenderMode::Full);
assert_eq!(config.prompt_format, PromptFormat::Markdown);
assert_eq!(config.max_skills_in_prompt, 15);
assert!(!config.enable_auto_trigger);
assert!(!config.enable_description_matching);
assert_eq!(config.min_keyword_matches, 3);
}
}