use serde_json::Value;
use std::collections::BTreeSet;
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum Dependency {
Agent(String),
Skill(String),
Tool(String),
Workflow(String),
}
pub fn find_agent_dependencies(agent: &Value) -> Vec<Dependency> {
let mut agent_tools = BTreeSet::new();
if let Some(tools) = agent
.get("configuration")
.and_then(|c| c.get("tools"))
.and_then(|t| t.as_array())
{
for tool in tools {
if let Some(tool_id) = tool.as_str() {
agent_tools.insert(tool_id.to_string());
} else if let Some(tool_ids) = tool.get("tool_ids").and_then(|ids| ids.as_array()) {
for id in tool_ids {
if let Some(id_str) = id.as_str() {
agent_tools.insert(id_str.to_string());
}
}
}
}
}
let mut skills = BTreeSet::new();
let mut ignored_agents = BTreeSet::new();
let mut tools = BTreeSet::new();
let mut ignored_workflows = BTreeSet::new();
recursive_find_deps(
agent,
&mut ignored_agents,
&mut skills,
&mut tools,
&mut ignored_workflows,
);
agent_tools.extend(tools);
let mut deps = Vec::new();
deps.extend(agent_tools.into_iter().map(Dependency::Tool));
deps.extend(skills.into_iter().map(Dependency::Skill));
deps
}
pub fn find_skill_dependencies(skill: &Value) -> Vec<Dependency> {
let mut deps = Vec::new();
if let Some(tool_ids) = skill.get("tool_ids").and_then(|ids| ids.as_array()) {
for id in tool_ids {
if let Some(id_str) = id.as_str() {
deps.push(Dependency::Tool(id_str.to_string()));
}
}
}
deps
}
pub fn find_tool_dependencies(tool: &Value) -> Vec<Dependency> {
let mut deps = Vec::new();
if let Some(workflow_id) = tool
.get("configuration")
.and_then(|c| c.get("workflow_id"))
.and_then(|w| w.as_str())
{
deps.push(Dependency::Workflow(workflow_id.to_string()));
}
deps
}
pub fn find_workflow_dependencies(workflow: &Value) -> Vec<Dependency> {
let mut agents = BTreeSet::new();
let mut skills = BTreeSet::new();
let mut tools = BTreeSet::new();
let mut workflows = BTreeSet::new();
recursive_find_deps(
workflow,
&mut agents,
&mut skills,
&mut tools,
&mut workflows,
);
let mut deps = Vec::new();
for id in agents {
deps.push(Dependency::Agent(id));
}
for id in tools {
deps.push(Dependency::Tool(id));
}
for id in skills {
deps.push(Dependency::Skill(id));
}
for id in workflows {
deps.push(Dependency::Workflow(id));
}
deps
}
fn recursive_find_deps(
value: &Value,
agents: &mut BTreeSet<String>,
skills: &mut BTreeSet<String>,
tools: &mut BTreeSet<String>,
workflows: &mut BTreeSet<String>,
) {
match value {
Value::Object(map) => {
for (k, v) in map {
match k.as_str() {
"agent_id" | "agentId" => {
if let Some(id) = v.as_str() {
agents.insert(id.to_string());
}
}
"skill_id" | "skillId" | "skill_ids" | "skillIds" => {
if let Some(id) = v.as_str() {
skills.insert(id.to_string());
} else if let Some(arr) = v.as_array() {
for item in arr {
if let Some(id) = item.as_str() {
skills.insert(id.to_string());
}
}
}
}
"tool_id" | "toolId" | "tool_ids" | "toolIds" => {
if let Some(id) = v.as_str() {
tools.insert(id.to_string());
} else if let Some(arr) = v.as_array() {
for item in arr {
if let Some(id) = item.as_str() {
tools.insert(id.to_string());
}
}
}
}
"workflow_id" | "workflowId" => {
if let Some(id) = v.as_str() {
workflows.insert(id.to_string());
}
}
_ => recursive_find_deps(v, agents, skills, tools, workflows),
}
}
}
Value::Array(arr) => {
for v in arr {
recursive_find_deps(v, agents, skills, tools, workflows);
}
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_find_agent_deps() {
let agent_simple = json!({
"id": "agent-1",
"configuration": {
"tools": ["tool-a", "tool-b"]
}
});
let deps = find_agent_dependencies(&agent_simple);
assert_eq!(deps.len(), 2);
assert!(deps.contains(&Dependency::Tool("tool-a".to_string())));
assert!(deps.contains(&Dependency::Tool("tool-b".to_string())));
let agent_nested = json!({
"id": "agent-2",
"configuration": {
"tools": [
{
"tool_ids": ["tool-c", "tool-d"]
}
]
}
});
let deps = find_agent_dependencies(&agent_nested);
assert_eq!(deps.len(), 2);
assert!(deps.contains(&Dependency::Tool("tool-c".to_string())));
assert!(deps.contains(&Dependency::Tool("tool-d".to_string())));
let agent_with_skills = json!({
"id": "agent-3",
"configuration": {
"skill_ids": ["skill-a", "skill-b"]
}
});
let deps = find_agent_dependencies(&agent_with_skills);
assert_eq!(deps.len(), 2);
assert!(deps.contains(&Dependency::Skill("skill-a".to_string())));
assert!(deps.contains(&Dependency::Skill("skill-b".to_string())));
let agent_with_generic_tool_keys = json!({
"id": "agent-4",
"configuration": {
"toolId": "tool-e",
"nested": {
"tool_ids": ["tool-f", "tool-g"]
}
}
});
let deps = find_agent_dependencies(&agent_with_generic_tool_keys);
assert_eq!(deps.len(), 3);
assert!(deps.contains(&Dependency::Tool("tool-e".to_string())));
assert!(deps.contains(&Dependency::Tool("tool-f".to_string())));
assert!(deps.contains(&Dependency::Tool("tool-g".to_string())));
}
#[test]
fn test_find_skill_deps() {
let skill = json!({
"id": "skill-1",
"tool_ids": ["tool-a", "tool-b"]
});
let deps = find_skill_dependencies(&skill);
assert_eq!(deps.len(), 2);
assert!(deps.contains(&Dependency::Tool("tool-a".to_string())));
assert!(deps.contains(&Dependency::Tool("tool-b".to_string())));
}
#[test]
fn test_find_tool_deps() {
let tool = json!({
"id": "tool-1",
"configuration": {
"workflow_id": "wf-1"
}
});
let deps = find_tool_dependencies(&tool);
assert_eq!(deps.len(), 1);
assert!(deps.contains(&Dependency::Workflow("wf-1".to_string())));
}
#[test]
fn test_find_workflow_deps() {
let workflow = json!({
"id": "wf-1",
"definition": {
"nodes": [
{ "agent_id": "agent-1" },
{ "skillId": "skill-a" },
{ "toolId": "tool-x" },
{ "tool_ids": ["tool-y", "tool-z"] },
{ "sub_workflow": { "workflowId": "wf-2" } }
]
}
});
let deps = find_workflow_dependencies(&workflow);
assert_eq!(deps.len(), 6);
assert!(deps.contains(&Dependency::Agent("agent-1".to_string())));
assert!(deps.contains(&Dependency::Skill("skill-a".to_string())));
assert!(deps.contains(&Dependency::Tool("tool-x".to_string())));
assert!(deps.contains(&Dependency::Tool("tool-y".to_string())));
assert!(deps.contains(&Dependency::Tool("tool-z".to_string())));
assert!(deps.contains(&Dependency::Workflow("wf-2".to_string())));
}
}