1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct AgentDefinition {
11 pub name: String,
13
14 #[serde(skip_serializing_if = "Option::is_none")]
16 pub description: Option<String>,
17
18 pub system_prompt: String,
20
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub model: Option<String>,
24
25 #[serde(default)]
27 pub tool_allowlist: Vec<String>,
28}
29
30impl AgentDefinition {
31 pub fn new(name: impl Into<String>, system_prompt: impl Into<String>) -> Self {
33 Self {
34 name: name.into(),
35 description: None,
36 system_prompt: system_prompt.into(),
37 model: None,
38 tool_allowlist: Vec::new(),
39 }
40 }
41
42 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
44 self.description = Some(desc.into());
45 self
46 }
47
48 pub fn with_model(mut self, model: impl Into<String>) -> Self {
50 self.model = Some(model.into());
51 self
52 }
53
54 pub fn with_tools(mut self, tools: Vec<String>) -> Self {
56 self.tool_allowlist = tools;
57 self
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
63#[serde(tag = "type", rename_all = "snake_case")]
64pub enum ControlRequest {
65 #[serde(rename = "permission_check")]
67 PermissionCheck(ToolPermissionRequest),
68
69 #[serde(rename = "hook")]
71 Hook {
72 event_type: String,
74 data: serde_json::Value,
76 },
77
78 #[serde(rename = "permission_mode")]
80 PermissionModeChange {
81 mode: PermissionMode,
83 },
84
85 #[serde(rename = "interrupt")]
87 Interrupt,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
92pub struct ToolPermissionRequest {
93 pub tool: String,
95
96 pub input: serde_json::Value,
98
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub cli_suggestion: Option<String>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
106pub struct ControlResponse {
107 pub request_id: String,
109
110 pub approved: bool,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub modified_input: Option<serde_json::Value>,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub reason: Option<String>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
124pub struct PermissionResponse {
125 pub allow: bool,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub modified_input: Option<serde_json::Value>,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub permission_request_suggestion: Option<String>,
135}
136
137impl PermissionResponse {
138 pub fn allow() -> Self {
140 Self {
141 allow: true,
142 modified_input: None,
143 permission_request_suggestion: None,
144 }
145 }
146
147 pub fn deny() -> Self {
149 Self {
150 allow: false,
151 modified_input: None,
152 permission_request_suggestion: None,
153 }
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
159#[serde(tag = "type", rename_all = "snake_case")]
160pub enum HookEvent {
161 #[serde(rename = "pre_tool_use")]
163 PreToolUse {
164 tool: ToolHookData,
166 },
167
168 #[serde(rename = "post_tool_use")]
170 PostToolUse {
171 tool: ToolHookData,
173 result: ToolResultHookData,
175 },
176
177 #[serde(rename = "user_prompt_submit")]
179 UserPromptSubmit {
180 prompt: String,
182 },
183
184 #[serde(rename = "stop")]
186 Stop,
187
188 #[serde(rename = "subagent_stop")]
190 SubagentStop,
191
192 #[serde(rename = "pre_compact")]
194 PreCompact,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
199pub struct ToolHookData {
200 pub id: String,
202
203 pub name: String,
205
206 pub input: serde_json::Value,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
212pub struct ToolResultHookData {
213 pub tool_use_id: String,
215
216 #[serde(skip_serializing_if = "Option::is_none")]
218 pub content: Option<String>,
219
220 #[serde(default)]
222 pub is_error: bool,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
227pub struct HookResponse {
228 pub continue_: bool,
230
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub modified_inputs: Option<serde_json::Value>,
234
235 #[serde(skip_serializing_if = "Option::is_none")]
237 pub context: Option<String>,
238
239 #[serde(default)]
241 pub hide_from_transcript: bool,
242}
243
244impl HookResponse {
245 pub fn continue_exec() -> Self {
247 Self {
248 continue_: true,
249 modified_inputs: None,
250 context: None,
251 hide_from_transcript: false,
252 }
253 }
254
255 pub fn stop() -> Self {
257 Self {
258 continue_: false,
259 modified_inputs: None,
260 context: None,
261 hide_from_transcript: false,
262 }
263 }
264}
265
266#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
268#[serde(rename_all = "snake_case")]
269pub enum PermissionMode {
270 Default,
272
273 AcceptEdits,
275
276 BypassPermissions,
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_agent_definition() {
286 let agent = AgentDefinition::new("DataAnalyzer", "Analyze data")
287 .with_description("Analyzes datasets")
288 .with_model("claude-3-5-sonnet")
289 .with_tools(vec!["bash".to_string(), "file_editor".to_string()]);
290
291 assert_eq!(agent.name, "DataAnalyzer");
292 assert_eq!(agent.tool_allowlist.len(), 2);
293 }
294
295 #[test]
296 fn test_permission_response_serialization() {
297 let resp = PermissionResponse::allow();
298 let json = serde_json::to_string(&resp).unwrap();
299 let deserialized: PermissionResponse = serde_json::from_str(&json).unwrap();
300 assert_eq!(resp, deserialized);
301 }
302
303 #[test]
304 fn test_hook_response() {
305 let resp = HookResponse::continue_exec();
306 assert!(resp.continue_);
307 let resp = HookResponse::stop();
308 assert!(!resp.continue_);
309 }
310}