1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub enum AgentMode {
7 Primary,
8 Subagent,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct AgentConfig {
13 pub name: String,
14 pub display_name: String,
15 pub description: String,
16 #[serde(default = "default_mode")]
17 pub mode: AgentMode,
18 #[serde(default)]
19 pub permission_overrides: BTreeMap<String, String>,
20 #[serde(default)]
21 pub model: Option<String>,
22 #[serde(default)]
23 pub color: Option<String>,
24 #[serde(default)]
26 pub system_prompt: Option<String>,
27}
28
29impl AgentConfig {
30 pub fn builtin_build() -> Self {
31 Self {
32 name: "build".to_string(),
33 display_name: "Build".to_string(),
34 description: "Build agent with standard permissions".to_string(),
35 mode: AgentMode::Primary,
36 system_prompt: Some(crate::core::system_prompt::build_system_prompt()),
37 permission_overrides: BTreeMap::new(),
38 model: None,
39 color: Some("blue".to_string()),
40 }
41 }
42
43 pub fn builtin_plan() -> Self {
44 let mut overrides = BTreeMap::new();
45 overrides.insert("write".to_string(), "deny".to_string());
46 overrides.insert("edit".to_string(), "deny".to_string());
47
48 Self {
49 name: "plan".to_string(),
50 display_name: "Plan".to_string(),
51 description: "Planning agent that analyzes without executing".to_string(),
52 mode: AgentMode::Primary,
53 system_prompt: Some(crate::core::system_prompt::plan_system_prompt()),
54 permission_overrides: overrides,
55 model: None,
56 color: Some("pink".to_string()),
57 }
58 }
59
60 pub fn builtin_explorer() -> Self {
61 let mut overrides = BTreeMap::new();
62 overrides.insert("write".to_string(), "deny".to_string());
63 overrides.insert("edit".to_string(), "deny".to_string());
64 overrides.insert("bash".to_string(), "deny".to_string());
65
66 Self {
67 name: "explorer".to_string(),
68 display_name: "Explorer".to_string(),
69 description:
70 "A fast, read-only agent for exploring codebases and answering code questions"
71 .to_string(),
72 mode: AgentMode::Subagent,
73 system_prompt: Some(crate::core::system_prompt::explorer_system_prompt()),
74 permission_overrides: overrides,
75 model: None,
76 color: Some("cyan".to_string()),
77 }
78 }
79
80 pub fn builtin_general() -> Self {
81 let mut overrides = BTreeMap::new();
82 overrides.insert("read".to_string(), "allow".to_string());
83 overrides.insert("list".to_string(), "allow".to_string());
84 overrides.insert("glob".to_string(), "allow".to_string());
85 overrides.insert("grep".to_string(), "allow".to_string());
86 overrides.insert("write".to_string(), "allow".to_string());
87 overrides.insert("edit".to_string(), "allow".to_string());
88 overrides.insert("question".to_string(), "allow".to_string());
89 overrides.insert("bash".to_string(), "allow".to_string());
90 overrides.insert("web".to_string(), "allow".to_string());
91 overrides.insert("task".to_string(), "allow".to_string());
92 overrides.insert("todo_write".to_string(), "deny".to_string());
93 overrides.insert("todo_read".to_string(), "deny".to_string());
94
95 Self {
96 name: "general".to_string(),
97 display_name: "General".to_string(),
98 description:
99 "A general-purpose subagent for complex research and multi-step parallel tasks"
100 .to_string(),
101 mode: AgentMode::Subagent,
102 system_prompt: Some(crate::core::system_prompt::general_system_prompt()),
103 permission_overrides: overrides,
104 model: None,
105 color: Some("green".to_string()),
106 }
107 }
108}
109
110fn default_mode() -> AgentMode {
111 AgentMode::Subagent
112}
113
114#[derive(Debug, Deserialize)]
116pub struct AgentFrontmatter {
117 pub display_name: String,
118 pub description: String,
119 #[serde(default = "default_mode")]
120 pub mode: AgentMode,
121 #[serde(default)]
122 pub model: Option<String>,
123 #[serde(default)]
124 pub color: Option<String>,
125 #[serde(default)]
126 pub tools: Option<BTreeMap<String, String>>,
127 #[serde(default)]
128 pub system_prompt: Option<String>,
129}
130
131impl AgentFrontmatter {
132 pub fn to_agent_config(&self, name: String, body: Option<String>) -> AgentConfig {
133 let mut permission_overrides = BTreeMap::new();
134 if let Some(tools) = &self.tools {
135 for (tool, policy) in tools {
136 permission_overrides.insert(tool.clone(), policy.clone());
137 }
138 }
139
140 let system_prompt = if let Some(body) = body {
142 if body.trim().is_empty() {
143 self.system_prompt.clone()
144 } else {
145 Some(body)
146 }
147 } else {
148 self.system_prompt.clone()
149 };
150
151 AgentConfig {
152 name,
153 display_name: self.display_name.clone(),
154 description: self.description.clone(),
155 mode: self.mode,
156 permission_overrides,
157 model: self.model.clone(),
158 color: self.color.clone(),
159 system_prompt,
160 }
161 }
162}