use crate::core::config::AgentConfig;
use crate::core::config::HandlerType;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Requirement {
HttpBind {
host: String,
port: u16,
},
McpHttpBind {
host: String,
port: u16,
},
McpCommand {
server: String,
command: String,
},
LlmProviderFromEnv,
UnknownHandler {
name: String,
},
}
pub fn requirements(config: &AgentConfig) -> Vec<Requirement> {
let mut requirements = Vec::new();
if config.server.http_port != 0 {
requirements.push(Requirement::HttpBind {
host: config.server.host.clone(),
port: config.server.http_port,
});
}
let mcp_server = &config.features.mcp_server;
if mcp_server.enabled && mcp_server.http.enabled {
requirements.push(Requirement::McpHttpBind {
host: mcp_server.http.host.clone(),
port: mcp_server.http.port,
});
}
if config.features.mcp_client.enabled {
for server in &config.features.mcp_client.servers {
requirements.push(Requirement::McpCommand {
server: server.name.clone(),
command: server.command.clone(),
});
}
}
match config.handler_type() {
HandlerType::Llm => {
let key_in_config = config
.llm
.as_ref()
.and_then(|llm| llm.api_key.as_deref())
.is_some_and(|key| !key.trim().is_empty());
if !key_in_config {
requirements.push(Requirement::LlmProviderFromEnv);
}
}
HandlerType::Custom(name) => requirements.push(Requirement::UnknownHandler { name }),
#[cfg(not(feature = "reimbursement-agent"))]
HandlerType::Reimbursement => requirements.push(Requirement::UnknownHandler {
name: "reimbursement".to_string(),
}),
#[cfg(feature = "reimbursement-agent")]
HandlerType::Reimbursement => {}
HandlerType::Echo => {}
}
requirements
}
#[cfg(test)]
mod tests {
use super::*;
fn config(toml: &str) -> AgentConfig {
AgentConfig::from_toml(toml).expect("fixture config parses")
}
#[test]
fn an_echo_agent_only_needs_its_port() {
let config = config(
r#"
[agent]
name = "Echo"
[server]
host = "127.0.0.1"
http_port = 8080
"#,
);
assert_eq!(
requirements(&config),
[Requirement::HttpBind {
host: "127.0.0.1".into(),
port: 8080
}]
);
}
#[test]
fn port_zero_needs_no_bind() {
let config = config(
r#"
[agent]
name = "Stdio Only"
[server]
http_port = 0
[features.mcp_server]
enabled = true
"#,
);
assert!(requirements(&config).is_empty());
}
#[test]
fn an_mcp_http_agent_needs_both_binds() {
let config = config(
r#"
[agent]
name = "Both"
[server]
host = "127.0.0.1"
http_port = 8080
[features.mcp_server]
enabled = true
[features.mcp_server.http]
enabled = true
host = "127.0.0.1"
port = 8000
"#,
);
assert_eq!(
requirements(&config),
[
Requirement::HttpBind {
host: "127.0.0.1".into(),
port: 8080
},
Requirement::McpHttpBind {
host: "127.0.0.1".into(),
port: 8000
},
]
);
}
#[test]
fn mcp_client_commands_are_requirements_only_when_enabled() {
let toml = r#"
[agent]
name = "Tools"
[server]
http_port = 8080
[features.mcp_client]
enabled = {enabled}
[[features.mcp_client.servers]]
name = "filesystem"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "."]
"#;
let enabled = config(&toml.replace("{enabled}", "true"));
assert!(requirements(&enabled).contains(&Requirement::McpCommand {
server: "filesystem".into(),
command: "npx".into(),
}));
let disabled = config(&toml.replace("{enabled}", "false"));
assert!(
!disabled.features.mcp_client.servers.is_empty(),
"the fixture must still declare a server"
);
assert!(
requirements(&disabled)
.iter()
.all(|r| !matches!(r, Requirement::McpCommand { .. }))
);
}
#[test]
fn an_llm_agent_needs_a_provider_from_the_environment() {
let config = config(
r#"
[agent]
name = "Chat"
[server]
http_port = 8080
[handler]
type = "llm"
"#,
);
assert!(requirements(&config).contains(&Requirement::LlmProviderFromEnv));
}
#[test]
fn a_configured_api_key_satisfies_the_provider() {
let config = config(
r#"
[agent]
name = "Chat"
[server]
http_port = 8080
[handler]
type = "llm"
[llm]
provider = "openai"
api_key = "sk-configured"
"#,
);
assert!(!requirements(&config).contains(&Requirement::LlmProviderFromEnv));
}
#[test]
fn an_empty_configured_key_still_needs_the_environment() {
let config = config(
r#"
[agent]
name = "Chat"
[server]
http_port = 8080
[handler]
type = "llm"
[llm]
provider = "openrouter"
api_key = " "
"#,
);
assert!(requirements(&config).contains(&Requirement::LlmProviderFromEnv));
}
#[test]
fn an_unknown_handler_is_reported() {
let config = config(
r#"
[agent]
name = "Custom"
[server]
http_port = 8080
[handler]
type = "weather"
"#,
);
assert!(
requirements(&config).contains(&Requirement::UnknownHandler {
name: "weather".into()
})
);
}
}