Skip to main content

systemprompt_models/services/
mod.rs

1//! `services` module — see crate-level docs for context.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6pub mod agent_config;
7pub mod ai;
8pub mod artifacts;
9pub mod bridge_policy;
10pub mod external_agent;
11pub mod frontmatter;
12pub mod hooks;
13mod includable;
14pub mod marketplace;
15pub mod mcp;
16pub mod plugin;
17pub mod runtime;
18pub mod scheduler;
19pub mod settings;
20pub mod skills;
21pub mod slack;
22pub mod system_admin;
23pub mod teams;
24mod validation;
25
26pub use includable::IncludableString;
27
28pub use agent_config::{
29    AGENT_CONFIG_FILENAME, AgentCardConfig, AgentConfig, AgentMetadataConfig, AgentProviderInfo,
30    AgentSummary, CapabilitiesConfig, DEFAULT_AGENT_SYSTEM_PROMPT_FILE, DiskAgentConfig,
31    OAuthConfig,
32};
33pub use ai::{
34    AiConfig, AiProviderConfig, HistoryConfig, McpConfig, ModelCapabilities, ModelDefinition,
35    ModelLimits, ModelPricing, ResilienceSettings, SamplingConfig,
36};
37pub use artifacts::{ARTIFACT_CONFIG_FILENAME, DEFAULT_ARTIFACT_CONTENT_FILE, DiskArtifactConfig};
38pub use bridge_policy::BridgePolicyConfig;
39pub use external_agent::{ExternalAgentConfig, ExternalAgentKind};
40pub use frontmatter::{Frontmatter, split_frontmatter, strip_frontmatter};
41pub use hooks::{
42    DiskHookConfig, HOOK_CONFIG_FILENAME, HookAction, HookCategory, HookEvent, HookEventsConfig,
43    HookMatcher, HookType,
44};
45pub use marketplace::{
46    MarketplaceAccess, MarketplaceConfig, MarketplaceConfigFile, MarketplaceVisibility,
47};
48pub use mcp::McpServerSummary;
49pub use plugin::{
50    ComponentFilter, ComponentSource, PluginAuthor, PluginComponentRef, PluginConfig,
51    PluginConfigFile, PluginHooksRef, PluginScript, PluginSummary, PluginVariableDef,
52};
53pub use runtime::{RuntimeStatus, ServiceType};
54pub use scheduler::*;
55pub use settings::*;
56pub use skills::{
57    DEFAULT_SKILL_CONTENT_FILE, DiskSkillConfig, SKILL_CONFIG_FILENAME, SkillConfig, SkillDetail,
58    SkillSummary, SkillsConfig,
59};
60pub use slack::{SlackAppConfig, SlackAuthzConfig};
61pub use system_admin::{SystemAdmin, SystemAdminConfig};
62pub use systemprompt_provider_contracts::{BrandingConfig, WebConfig};
63pub use teams::{TeamsAppConfig, TeamsAuthzConfig};
64
65use crate::errors::ConfigValidationError;
66use crate::mcp::{Deployment, McpServerType};
67use serde::{Deserialize, Serialize};
68use std::collections::HashMap;
69use systemprompt_identifiers::{ExternalAgentId, MarketplaceId};
70
71#[derive(Debug, Clone, Default, Serialize, Deserialize)]
72#[serde(deny_unknown_fields)]
73pub struct ServicesConfig {
74    #[serde(default)]
75    pub includes: Vec<String>,
76    #[serde(default)]
77    pub settings: Settings,
78    #[serde(default)]
79    pub agents: HashMap<String, AgentConfig>,
80    #[serde(default)]
81    pub mcp_servers: HashMap<String, Deployment>,
82    #[serde(default)]
83    pub scheduler: Option<SchedulerConfig>,
84    #[serde(default)]
85    pub ai: AiConfig,
86    #[serde(default)]
87    pub web: Option<WebConfig>,
88    #[serde(default)]
89    pub plugins: HashMap<String, PluginConfig>,
90    #[serde(default)]
91    pub marketplaces: HashMap<MarketplaceId, MarketplaceConfig>,
92    #[serde(default)]
93    pub skills: SkillsConfig,
94    #[serde(default)]
95    pub external_agents: HashMap<ExternalAgentId, ExternalAgentConfig>,
96    #[serde(default)]
97    pub slack_apps: HashMap<String, SlackAppConfig>,
98    #[serde(default)]
99    pub teams_apps: HashMap<String, TeamsAppConfig>,
100    #[serde(default)]
101    pub bridge_policy: Option<BridgePolicyConfig>,
102}
103
104impl ServicesConfig {
105    pub fn apply_port_offset(&mut self, offset: u16) -> Result<(), ConfigValidationError> {
106        if offset == 0 {
107            return Ok(());
108        }
109
110        let shift = |port: u16, what: &str| {
111            port.checked_add(offset).ok_or_else(|| {
112                ConfigValidationError::invalid_field(format!(
113                    "{what} port {port} shifted by services.port_offset {offset} exceeds 65535"
114                ))
115            })
116        };
117
118        for (name, agent) in &mut self.agents {
119            agent.port = shift(agent.port, &format!("Agent '{name}'"))?;
120        }
121
122        for (name, mcp) in &mut self.mcp_servers {
123            if mcp.server_type == McpServerType::External {
124                continue;
125            }
126            mcp.port = shift(mcp.port, &format!("MCP server '{name}'"))?;
127        }
128
129        self.settings.agent_port_range = (
130            shift(self.settings.agent_port_range.0, "agent_port_range lower")?,
131            shift(self.settings.agent_port_range.1, "agent_port_range upper")?,
132        );
133        self.settings.mcp_port_range = (
134            shift(self.settings.mcp_port_range.0, "mcp_port_range lower")?,
135            shift(self.settings.mcp_port_range.1, "mcp_port_range upper")?,
136        );
137
138        Ok(())
139    }
140
141    pub fn validate(&self) -> Result<(), ConfigValidationError> {
142        self.validate_ports()?;
143        self.validate_single_default_agent()?;
144
145        for (name, agent) in &self.agents {
146            agent.validate(name)?;
147        }
148
149        for (name, mcp) in &self.mcp_servers {
150            mcp.validate(name)?;
151        }
152
153        for (name, plugin) in &self.plugins {
154            plugin.validate(name)?;
155            self.validate_plugin_bindings(name, plugin)?;
156        }
157
158        self.validate_single_governance_hook_owner()?;
159
160        for (id, marketplace) in &self.marketplaces {
161            marketplace.validate(id.as_str())?;
162            self.validate_marketplace_bindings(id.as_str(), marketplace)?;
163        }
164
165        self.validate_default_marketplace_selector()?;
166
167        for (name, app) in &self.slack_apps {
168            app.validate(name)?;
169        }
170
171        for (name, app) in &self.teams_apps {
172            app.validate(name)?;
173        }
174
175        Ok(())
176    }
177}