enact_config/
agent_def.rs1use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5use std::path::{Path, PathBuf};
6
7use crate::config::{ApprovalConfig, MemoryConfig, SessionConfigOverride};
8use crate::home::enact_home;
9use crate::hook_config::HookConfig;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
14#[serde(deny_unknown_fields)]
15pub struct ChannelBotConfig {
16 #[serde(default)]
18 pub bot_name: Option<String>,
19 #[serde(default)]
22 pub bot_token: Option<String>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
27#[serde(deny_unknown_fields)]
28pub struct AgentDef {
29 pub name: String,
30 #[serde(default)]
31 pub description: Option<String>,
32 #[serde(default = "default_version")]
33 pub version: String,
34 #[serde(default)]
36 pub model: Option<String>,
37 #[serde(default)]
38 pub system_prompt: Option<String>,
39 #[serde(default)]
40 pub tools: Vec<String>,
41 #[serde(default)]
43 pub workflow: Option<String>,
44 #[serde(default)]
46 pub approval: Option<ApprovalConfig>,
47 #[serde(default)]
48 pub memory: Option<MemoryConfig>,
49 #[serde(default)]
52 pub channels: Vec<String>,
53 #[serde(default)]
56 pub telegram: Option<ChannelBotConfig>,
57 #[serde(default)]
60 pub whatsapp: Option<ChannelBotConfig>,
61 #[serde(default)]
64 pub teams: Option<ChannelBotConfig>,
65 #[serde(default)]
67 pub session: Option<SessionConfigOverride>,
68 #[serde(default)]
70 pub hooks: Option<Vec<HookConfig>>,
71}
72
73fn default_version() -> String {
74 "1.0.0".to_string()
75}
76
77impl Default for AgentDef {
78 fn default() -> Self {
79 Self {
80 name: String::new(),
81 description: None,
82 version: default_version(),
83 model: None,
84 system_prompt: None,
85 tools: Vec::new(),
86 workflow: None,
87 approval: None,
88 memory: None,
89 channels: Vec::new(),
90 telegram: None,
91 whatsapp: None,
92 teams: None,
93 session: None,
94 hooks: None,
95 }
96 }
97}
98
99impl AgentDef {
100 pub fn agent_dir(home: &Path, name: &str) -> PathBuf {
102 home.join("agents").join(name)
103 }
104
105 pub fn agent_yaml_path(home: &Path, name: &str) -> PathBuf {
107 Self::agent_dir(home, name).join("agent.yaml")
108 }
109
110 pub fn sessions_dir(home: &Path, name: &str) -> PathBuf {
112 Self::agent_dir(home, name).join("sessions")
113 }
114
115 pub fn memory_dir(home: &Path, name: &str) -> PathBuf {
117 Self::agent_dir(home, name).join("memory")
118 }
119
120 pub fn threads_dir(home: &Path, agent_name: &str) -> PathBuf {
122 Self::agent_dir(home, agent_name).join("threads")
123 }
124
125 pub fn load(home: &Path, name: &str) -> Result<Option<Self>> {
127 let path = Self::agent_yaml_path(home, name);
128 if !path.exists() {
129 return Ok(None);
130 }
131 let s = std::fs::read_to_string(&path).context("Failed to read agent.yaml")?;
132 let def: AgentDef = serde_yaml::from_str(&s).context("Failed to parse agent.yaml")?;
133 Ok(Some(def))
134 }
135
136 pub fn save(&self, home: &Path) -> Result<()> {
138 let dir = Self::agent_dir(home, &self.name);
139 std::fs::create_dir_all(&dir).context("Failed to create agent directory")?;
140 let path = dir.join("agent.yaml");
141 let s = serde_yaml::to_string(self).context("Failed to serialize agent to YAML")?;
142 std::fs::write(&path, s).context("Failed to write agent.yaml")?;
143 Ok(())
144 }
145}
146
147pub struct AgentRegistry;
149
150impl AgentRegistry {
151 pub fn list(home: &Path) -> Result<Vec<String>> {
153 let agents_dir = home.join("agents");
154 if !agents_dir.exists() {
155 return Ok(Vec::new());
156 }
157 let mut names = Vec::new();
158 for e in std::fs::read_dir(agents_dir).context("Failed to read agents directory")? {
159 let e = e?;
160 let path = e.path();
161 if path.is_dir() && path.join("agent.yaml").exists() {
162 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
163 names.push(name.to_string());
164 }
165 }
166 }
167 names.sort();
168 Ok(names)
169 }
170
171 pub fn get(home: &Path, name: &str) -> Result<Option<AgentDef>> {
173 AgentDef::load(home, name)
174 }
175
176 pub fn get_default(name: &str) -> Result<Option<AgentDef>> {
178 Self::get(&enact_home(), name)
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn agent_dir_path() {
188 let home = Path::new("/tmp/.enact");
189 assert_eq!(
190 AgentDef::agent_yaml_path(home, "assistant"),
191 PathBuf::from("/tmp/.enact/agents/assistant/agent.yaml")
192 );
193 }
194}