use everruns_capability::is_plugin_capability;
use super::*;
pub const MAX_RESOLVED_CAPABILITIES: usize = 100;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DependencyError {
CircularDependency {
capability_id: String,
chain: Vec<String>,
},
TooManyCapabilities {
count: usize,
max: usize,
},
}
impl std::fmt::Display for DependencyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DependencyError::CircularDependency {
capability_id,
chain,
} => {
write!(
f,
"Circular dependency detected: {} depends on itself via chain: {} -> {}",
capability_id,
chain.join(" -> "),
capability_id
)
}
DependencyError::TooManyCapabilities { count, max } => {
write!(
f,
"Too many capabilities after resolution: {} (max: {})",
count, max
)
}
}
}
}
impl std::error::Error for DependencyError {}
#[derive(Debug, Clone)]
pub struct ResolvedCapabilities {
pub resolved_ids: Vec<String>,
pub added_as_dependencies: Vec<String>,
pub user_selected: Vec<String>,
}
pub fn resolve_dependencies(
selected_ids: &[String],
registry: &CapabilityRegistry,
) -> Result<ResolvedCapabilities, DependencyError> {
use std::collections::HashSet;
let user_selected: HashSet<String> = selected_ids
.iter()
.map(|id| registry.canonical_id(id).unwrap_or(id).to_string())
.collect();
let mut resolved: Vec<String> = Vec::new();
let mut resolved_set: HashSet<String> = HashSet::new();
let mut added_as_dependencies: Vec<String> = Vec::new();
for cap_id in selected_ids {
resolve_single_capability(
cap_id,
registry,
&mut resolved,
&mut resolved_set,
&mut added_as_dependencies,
&user_selected,
&mut Vec::new(), )?;
}
if resolved.len() > MAX_RESOLVED_CAPABILITIES {
return Err(DependencyError::TooManyCapabilities {
count: resolved.len(),
max: MAX_RESOLVED_CAPABILITIES,
});
}
Ok(ResolvedCapabilities {
resolved_ids: resolved,
added_as_dependencies,
user_selected: selected_ids.to_vec(),
})
}
pub fn resolve_capability_configs(
selected_configs: &[AgentCapabilityConfig],
registry: &CapabilityRegistry,
) -> Result<Vec<AgentCapabilityConfig>, DependencyError> {
let mut selected_ids: Vec<String> = Vec::new();
for config in selected_configs {
if (is_declarative_capability(config.capability_id())
|| is_plugin_capability(config.capability_id()))
&& let Ok(definition) = serde_json::from_value::<DeclarativeCapabilityDefinition>(
config.config_value().clone(),
)
{
selected_ids.extend(definition.dependencies);
}
selected_ids.push(config.capability_id().to_string());
}
let resolved = resolve_dependencies(&selected_ids, registry)?;
let explicit_configs: std::collections::HashMap<String, serde_json::Value> = selected_configs
.iter()
.map(|config| {
let id = config.capability_id();
let id = registry.canonical_id(id).unwrap_or(id);
(id.to_string(), config.config_value().clone())
})
.collect();
Ok(resolved
.resolved_ids
.into_iter()
.map(|capability_id| {
explicit_configs
.get(&capability_id)
.cloned()
.map(|config| AgentCapabilityConfig::with_config(capability_id.clone(), config))
.unwrap_or_else(|| AgentCapabilityConfig::new(capability_id))
})
.collect())
}
pub(crate) fn resolve_single_capability(
cap_id: &str,
registry: &CapabilityRegistry,
resolved: &mut Vec<String>,
resolved_set: &mut std::collections::HashSet<String>,
added_as_dependencies: &mut Vec<String>,
user_selected: &std::collections::HashSet<String>,
visiting: &mut Vec<String>,
) -> Result<(), DependencyError> {
let cap_id = registry.canonical_id(cap_id).unwrap_or(cap_id);
if resolved_set.contains(cap_id) {
return Ok(());
}
if visiting.contains(&cap_id.to_string()) {
return Err(DependencyError::CircularDependency {
capability_id: cap_id.to_string(),
chain: visiting.clone(),
});
}
let capability = match registry.get(cap_id) {
Some(cap) => cap,
None => {
if (is_declarative_capability(cap_id) || is_plugin_capability(cap_id))
&& !resolved_set.contains(cap_id)
{
resolved.push(cap_id.to_string());
resolved_set.insert(cap_id.to_string());
if !user_selected.contains(cap_id) {
added_as_dependencies.push(cap_id.to_string());
}
}
return Ok(());
}
};
visiting.push(cap_id.to_string());
for dep_id in capability.dependencies() {
resolve_single_capability(
dep_id,
registry,
resolved,
resolved_set,
added_as_dependencies,
user_selected,
visiting,
)?;
}
visiting.pop();
if !resolved_set.contains(cap_id) {
resolved.push(cap_id.to_string());
resolved_set.insert(cap_id.to_string());
if !user_selected.contains(cap_id) {
added_as_dependencies.push(cap_id.to_string());
}
}
Ok(())
}
pub fn compute_features(capability_ids: &[String], registry: &CapabilityRegistry) -> Vec<String> {
use std::collections::HashSet;
let resolved_ids = match resolve_dependencies(capability_ids, registry) {
Ok(resolved) => resolved.resolved_ids,
Err(_) => capability_ids.to_vec(),
};
let mut seen = HashSet::new();
let mut features = Vec::new();
for cap_id in &resolved_ids {
if let Some(cap) = registry.get(cap_id) {
for feature in cap.features() {
if seen.insert(feature) {
features.push(feature.to_string());
}
}
}
}
features
}
pub fn get_dependencies(cap_id: &str, registry: &CapabilityRegistry) -> Vec<String> {
registry
.get(cap_id)
.map(|cap| cap.dependencies().iter().map(|s| s.to_string()).collect())
.unwrap_or_default()
}