systemprompt_models/validators/
ai.rs1use super::ValidationConfigProvider;
4use crate::ServicesConfig;
5use systemprompt_traits::validation_report::{
6 ValidationError, ValidationReport, ValidationWarning,
7};
8use systemprompt_traits::{ConfigProvider, DomainConfig, DomainConfigError};
9
10#[derive(Debug, Default)]
11pub struct AiConfigValidator {
12 config: Option<ServicesConfig>,
13}
14
15impl AiConfigValidator {
16 pub fn new() -> Self {
17 Self::default()
18 }
19}
20
21impl DomainConfig for AiConfigValidator {
22 fn domain_id(&self) -> &'static str {
23 "ai"
24 }
25
26 fn priority(&self) -> u32 {
27 50
28 }
29
30 fn dependencies(&self) -> &[&'static str] {
31 &["mcp"]
32 }
33
34 fn load(&mut self, config: &dyn ConfigProvider) -> Result<(), DomainConfigError> {
35 let provider = config
36 .as_any()
37 .downcast_ref::<ValidationConfigProvider>()
38 .ok_or_else(|| {
39 DomainConfigError::LoadError(
40 "Expected ValidationConfigProvider with merged ServicesConfig".into(),
41 )
42 })?;
43
44 self.config = Some(provider.services_config().clone());
45 Ok(())
46 }
47
48 fn validate(&self) -> Result<ValidationReport, DomainConfigError> {
49 let mut report = ValidationReport::new("ai");
50 let config = self
51 .config
52 .as_ref()
53 .ok_or_else(|| DomainConfigError::ValidationError("Not loaded".into()))?;
54 let ai_config = &config.ai;
55
56 Self::validate_default_provider(&mut report, ai_config);
57 Self::validate_enabled_providers(&mut report, ai_config);
58 Self::validate_mcp_config(&mut report, ai_config);
59
60 if ai_config.history.retention_days == 0 {
61 report.add_warning(ValidationWarning::new(
62 "ai.history.retention_days",
63 "History retention set to 0 days, history will not be retained",
64 ));
65 }
66
67 Ok(report)
68 }
69}
70
71impl AiConfigValidator {
72 fn validate_default_provider(report: &mut ValidationReport, ai_config: &crate::AiConfig) {
73 if ai_config.default_provider.is_empty() {
74 report.add_error(ValidationError::new(
75 "ai.default_provider",
76 "Default AI provider not configured",
77 ));
78 } else if !ai_config
79 .providers
80 .contains_key(&ai_config.default_provider)
81 {
82 report.add_error(
83 ValidationError::new(
84 "ai.default_provider",
85 format!(
86 "Default provider '{}' not found in providers",
87 ai_config.default_provider
88 ),
89 )
90 .with_suggestion("Add the provider to ai.providers or change default_provider"),
91 );
92 }
93 }
94
95 fn validate_enabled_providers(report: &mut ValidationReport, ai_config: &crate::AiConfig) {
96 let enabled: Vec<_> = ai_config
97 .providers
98 .iter()
99 .filter(|(_, c)| c.enabled)
100 .collect();
101
102 if enabled.is_empty() {
103 report.add_error(
104 ValidationError::new("ai.providers", "No AI providers are enabled")
105 .with_suggestion("Enable at least one provider in ai.providers"),
106 );
107 }
108
109 for (name, cfg) in &enabled {
114 if cfg.default_model.is_empty() {
115 report.add_warning(ValidationWarning::new(
116 format!("ai.providers.{}.default_model", name),
117 format!(
118 "Provider '{}' has no default_model override; the provider client default \
119 will be used",
120 name
121 ),
122 ));
123 }
124 }
125 }
126
127 fn validate_mcp_config(report: &mut ValidationReport, ai_config: &crate::AiConfig) {
128 if ai_config.mcp.resilience.connect_timeout_ms == 0 {
129 report.add_error(ValidationError::new(
130 "ai.mcp.resilience.connect_timeout_ms",
131 "MCP connect timeout must be greater than 0",
132 ));
133 }
134 if ai_config.mcp.resilience.request_timeout_ms == 0 {
135 report.add_error(ValidationError::new(
136 "ai.mcp.resilience.request_timeout_ms",
137 "MCP execution timeout must be greater than 0",
138 ));
139 }
140 }
141}