systemprompt_cli/commands/core/plugins/generate/
agents.rs1use anyhow::Result;
7use std::path::Path;
8use systemprompt_models::{ComponentSource, PluginConfig};
9
10use super::DEFAULT_AGENT_TOOLS;
11
12pub fn generate_agents(
13 plugin: &PluginConfig,
14 services_path: &Path,
15 output_dir: &Path,
16 files_generated: &mut Vec<String>,
17) -> Result<()> {
18 let agents_dir = output_dir.join("agents");
19
20 let agents = resolve_agents(plugin, services_path)?;
21
22 if agents.is_empty() {
23 return Ok(());
24 }
25
26 std::fs::create_dir_all(&agents_dir)?;
27
28 let services_agents_dir = services_path.join("agents");
29
30 for agent in &agents {
31 let agent_md = build_agent_md(agent, &services_agents_dir)?;
32 let agent_path = agents_dir.join(format!("{agent}.md"));
33 std::fs::write(&agent_path, &agent_md)?;
34 files_generated.push(agent_path.to_string_lossy().to_string());
35 }
36
37 Ok(())
38}
39
40fn resolve_agents(plugin: &PluginConfig, services_path: &Path) -> Result<Vec<String>> {
41 if plugin.agents.source == ComponentSource::Explicit {
42 return Ok(plugin.agents.include.clone());
43 }
44
45 let agents_config_path = services_path.join("config").join("config.yaml");
46 if !agents_config_path.exists() {
47 return Ok(Vec::new());
48 }
49
50 let content = std::fs::read_to_string(&agents_config_path)?;
51 let config: serde_yaml::Value = serde_yaml::from_str(&content)?;
52
53 let mut ids = Vec::new();
54 if let Some(agents) = config.get("agents").and_then(|a| a.as_mapping()) {
55 for (key, _) in agents {
56 if let Some(name) = key.as_str()
57 && !plugin.agents.exclude.contains(&name.to_owned())
58 {
59 ids.push(name.to_owned());
60 }
61 }
62 }
63
64 ids.sort();
65 Ok(ids)
66}
67
68fn build_agent_md(agent: &str, services_agents_dir: &Path) -> Result<String> {
69 if services_agents_dir.exists() {
70 for entry in std::fs::read_dir(services_agents_dir)? {
71 let entry = entry?;
72 let path = entry.path();
73 let ext = path.extension().and_then(|e| e.to_str());
74 if ext != Some("yaml") && ext != Some("yml") {
75 continue;
76 }
77 let content = std::fs::read_to_string(&path)?;
78 let config: serde_yaml::Value = match serde_yaml::from_str(&content) {
79 Ok(c) => c,
80 Err(e) => {
81 tracing::warn!(path = %path.display(), error = %e, "Failed to parse YAML");
82 continue;
83 },
84 };
85 if let Some(agent_val) = config.get("agents").and_then(|a| a.get(agent)) {
86 let description = agent_val
87 .get("card")
88 .and_then(|c| c.get("description"))
89 .and_then(|d| d.as_str())
90 .map_or_else(|| format!("{agent} agent"), str::to_owned);
91 let system_prompt = agent_val
92 .get("metadata")
93 .and_then(|m| m.get("systemPrompt"))
94 .and_then(|s| s.as_str())
95 .map_or_else(String::new, str::to_owned);
96 return Ok(format!(
97 "---\nname: {}\ndescription: \"{}\"\ntools: {}\n---\n\n{}\n",
98 agent,
99 description.replace('"', "\\\""),
100 DEFAULT_AGENT_TOOLS,
101 system_prompt.trim()
102 ));
103 }
104 }
105 }
106
107 Ok(format!(
108 "---\nname: {}\ndescription: \"{} agent\"\ntools: {}\n---\n\nYou are the {} agent.\n",
109 agent, agent, DEFAULT_AGENT_TOOLS, agent
110 ))
111}