systemprompt_models/services/
mod.rs1pub mod agent_config;
7pub mod ai;
8pub mod artifacts;
9pub mod bridge_policy;
10pub mod external_agent;
11pub mod frontmatter;
12pub mod gateway;
13pub mod hooks;
14mod includable;
15pub mod marketplace;
16pub mod mcp;
17pub mod plugin;
18pub mod providers;
19pub mod runtime;
20pub mod scheduler;
21pub mod settings;
22pub mod skills;
23pub mod slack;
24pub mod system_admin;
25pub mod teams;
26mod validation;
27
28pub use includable::IncludableString;
29
30pub use agent_config::{
31 AGENT_CONFIG_FILENAME, AgentCardConfig, AgentConfig, AgentMetadataConfig, AgentProviderInfo,
32 AgentSummary, CapabilitiesConfig, DEFAULT_AGENT_SYSTEM_PROMPT_FILE, DiskAgentConfig,
33 OAuthConfig,
34};
35pub use ai::{
36 AiConfig, AiProviderConfig, HistoryConfig, McpConfig, ModelCapabilities, ModelDefinition,
37 ModelGovernance, ModelLimits, ModelPricing, ResilienceSettings, SamplingConfig,
38};
39pub use artifacts::{ARTIFACT_CONFIG_FILENAME, DEFAULT_ARTIFACT_CONTENT_FILE, DiskArtifactConfig};
40pub use bridge_policy::BridgePolicyConfig;
41pub use external_agent::{ExternalAgentConfig, ExternalAgentKind};
42pub use frontmatter::{Frontmatter, split_frontmatter, strip_frontmatter};
43pub use gateway::{
44 BridgeReleasesSpec, GatewayConfig, GatewayConfigSpec, GatewayProfileError, GatewayResult,
45 GatewayRoute, GatewayState, OverrideRuleAction, ResponseFormatKind, RouteMatch,
46 RouteRequirements, SystemPromptRule, slugify_pattern, synthesize_route_id,
47};
48pub use hooks::{
49 DiskHookConfig, HOOK_CONFIG_FILENAME, HookAction, HookCategory, HookEvent, HookEventsConfig,
50 HookMatcher, HookType,
51};
52pub use marketplace::{
53 MarketplaceAccess, MarketplaceConfig, MarketplaceConfigFile, MarketplaceMemberKind,
54 MarketplaceVisibility,
55};
56pub use mcp::McpServerSummary;
57pub use plugin::{
58 ComponentFilter, ComponentSource, PluginAuthor, PluginComponentRef, PluginConfig,
59 PluginConfigFile, PluginHooksRef, PluginScript, PluginSummary, PluginVariableDef,
60};
61pub use providers::{
62 ApiSurface, ProviderEntry, ProviderModel, ProviderRegistry, ProviderRegistryError,
63 ProviderRegistryResult, WireProtocol,
64};
65pub use runtime::{RuntimeStatus, ServiceType};
66pub use scheduler::*;
67pub use settings::*;
68pub use skills::{
69 DEFAULT_SKILL_CONTENT_FILE, DiskSkillConfig, SKILL_CONFIG_FILENAME, SkillConfig, SkillDetail,
70 SkillSummary, SkillsConfig,
71};
72pub use slack::{SlackAppConfig, SlackAuthzConfig};
73pub use system_admin::{SystemAdmin, SystemAdminConfig};
74pub use systemprompt_provider_contracts::{BrandingConfig, WebConfig};
75pub use teams::{TeamsAppConfig, TeamsAuthzConfig};
76
77use crate::errors::ConfigValidationError;
78use crate::mcp::{Deployment, McpServerType};
79use serde::{Deserialize, Serialize};
80use std::collections::HashMap;
81use systemprompt_identifiers::{ExternalAgentId, MarketplaceId};
82
83#[derive(Debug, Clone, Default, Serialize, Deserialize)]
84#[serde(deny_unknown_fields)]
85pub struct ServicesConfig {
86 #[serde(default)]
87 pub includes: Vec<String>,
88 #[serde(default)]
89 pub settings: Settings,
90 #[serde(default)]
91 pub agents: HashMap<String, AgentConfig>,
92 #[serde(default)]
93 pub mcp_servers: HashMap<String, Deployment>,
94 #[serde(default)]
95 pub scheduler: Option<SchedulerConfig>,
96 #[serde(default)]
97 pub ai: AiConfig,
98 #[serde(default)]
99 pub web: Option<WebConfig>,
100 #[serde(default)]
101 pub plugins: HashMap<String, PluginConfig>,
102 #[serde(default)]
103 pub marketplaces: HashMap<MarketplaceId, MarketplaceConfig>,
104 #[serde(default)]
105 pub skills: SkillsConfig,
106 #[serde(default)]
107 pub external_agents: HashMap<ExternalAgentId, ExternalAgentConfig>,
108 #[serde(default)]
109 pub slack_apps: HashMap<String, SlackAppConfig>,
110 #[serde(default)]
111 pub teams_apps: HashMap<String, TeamsAppConfig>,
112 #[serde(default)]
113 pub bridge_policy: Option<BridgePolicyConfig>,
114 #[serde(default)]
115 pub providers: ProviderRegistry,
116 #[serde(default)]
117 pub gateway: Option<GatewayState>,
118}
119
120impl ServicesConfig {
121 pub fn apply_port_offset(&mut self, offset: u16) -> Result<(), ConfigValidationError> {
122 if offset == 0 {
123 return Ok(());
124 }
125
126 let shift = |port: u16, what: &str| {
127 port.checked_add(offset).ok_or_else(|| {
128 ConfigValidationError::invalid_field(format!(
129 "{what} port {port} shifted by services.port_offset {offset} exceeds 65535"
130 ))
131 })
132 };
133
134 for (name, agent) in &mut self.agents {
135 agent.port = shift(agent.port, &format!("Agent '{name}'"))?;
136 }
137
138 for (name, mcp) in &mut self.mcp_servers {
139 if mcp.server_type == McpServerType::External {
140 continue;
141 }
142 mcp.port = shift(mcp.port, &format!("MCP server '{name}'"))?;
143 }
144
145 self.settings.agent_port_range = (
146 shift(self.settings.agent_port_range.0, "agent_port_range lower")?,
147 shift(self.settings.agent_port_range.1, "agent_port_range upper")?,
148 );
149 self.settings.mcp_port_range = (
150 shift(self.settings.mcp_port_range.0, "mcp_port_range lower")?,
151 shift(self.settings.mcp_port_range.1, "mcp_port_range upper")?,
152 );
153
154 Ok(())
155 }
156
157 pub fn validate(&self) -> Result<(), ConfigValidationError> {
158 self.validate_ports()?;
159 self.validate_single_default_agent()?;
160
161 for (name, agent) in &self.agents {
162 agent.validate(name)?;
163 }
164
165 for (name, mcp) in &self.mcp_servers {
166 mcp.validate(name)?;
167 }
168
169 self.validate_skills()?;
170
171 for (name, plugin) in &self.plugins {
172 plugin.validate(name)?;
173 self.validate_plugin_bindings(name, plugin)?;
174 }
175
176 self.validate_single_governance_hook_owner()?;
177
178 for (id, marketplace) in &self.marketplaces {
179 marketplace.validate(id.as_str())?;
180 self.validate_marketplace_bindings(id.as_str(), marketplace)?;
181 }
182
183 self.validate_default_marketplace_selector()?;
184
185 for (name, app) in &self.slack_apps {
186 app.validate(name)?;
187 }
188
189 for (name, app) in &self.teams_apps {
190 app.validate(name)?;
191 }
192
193 self.validate_providers_and_gateway()
194 }
195
196 fn validate_providers_and_gateway(&self) -> Result<(), ConfigValidationError> {
202 self.providers
203 .validate()
204 .map_err(|e| ConfigValidationError::invalid_field(format!("providers: {e}")))?;
205 match &self.gateway {
206 Some(GatewayState::Resolved(config)) => config.validate(&self.providers),
207 Some(GatewayState::Spec(spec)) => spec.clone().resolve().validate(&self.providers),
208 None => Ok(()),
209 }
210 .map_err(|e| ConfigValidationError::invalid_field(format!("gateway: {e}")))
211 }
212
213 #[must_use]
214 pub fn gateway_config(&self) -> Option<&GatewayConfig> {
215 self.gateway.as_ref().and_then(GatewayState::resolved)
216 }
217
218 #[must_use]
219 pub fn marketplace_plugin_configs(
220 &self,
221 marketplace: &MarketplaceConfig,
222 ) -> Vec<&PluginConfig> {
223 self.plugins
224 .values()
225 .filter(|p| p.enabled)
226 .filter(|p| {
227 marketplace.plugins.include.is_empty()
228 || marketplace
229 .plugins
230 .include
231 .iter()
232 .any(|inc| inc == p.id.as_str())
233 })
234 .collect()
235 }
236
237 #[must_use]
238 pub fn plugin_selected_skill_ids(
239 &self,
240 plugin: &PluginConfig,
241 ) -> std::collections::BTreeSet<String> {
242 let mut ids: std::collections::BTreeSet<String> = match plugin.skills.source {
243 ComponentSource::Explicit => plugin.skills.include.iter().cloned().collect(),
244 ComponentSource::Instance => self
245 .skills
246 .skills
247 .keys()
248 .filter(|k| !plugin.skills.exclude.iter().any(|ex| ex == *k))
249 .cloned()
250 .collect(),
251 };
252
253 let selected_agent = |name: &str| match plugin.agents.source {
254 ComponentSource::Explicit => plugin.agents.include.iter().any(|inc| inc == name),
255 ComponentSource::Instance => !plugin.agents.exclude.iter().any(|ex| ex == name),
256 };
257 for (name, agent) in &self.agents {
258 if selected_agent(name) {
259 ids.extend(agent.metadata.skills.include.iter().cloned());
260 }
261 }
262
263 ids
264 }
265
266 #[must_use]
267 pub fn marketplace_skill_members(
268 &self,
269 marketplace: &MarketplaceConfig,
270 ) -> std::collections::BTreeSet<String> {
271 self.marketplace_plugin_configs(marketplace)
272 .into_iter()
273 .flat_map(|plugin| self.plugin_selected_skill_ids(plugin))
274 .collect()
275 }
276}