turboclaude_protocol/
agent.rs

1//! Agent protocol types and definitions
2//!
3//! Provides types specific to the agent SDK protocol, including control requests,
4//! hooks, permissions, and agent definitions.
5
6use serde::{Deserialize, Serialize};
7
8/// Agent definition for specialized agent personas
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct AgentDefinition {
11    /// Unique name for the agent
12    pub name: String,
13
14    /// Description of the agent's purpose
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub description: Option<String>,
17
18    /// System prompt for the agent
19    pub system_prompt: String,
20
21    /// Model to use for this agent
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub model: Option<String>,
24
25    /// List of tools the agent can use
26    #[serde(default)]
27    pub tool_allowlist: Vec<String>,
28}
29
30impl AgentDefinition {
31    /// Create a new agent definition
32    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    /// Set the description
43    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
44        self.description = Some(desc.into());
45        self
46    }
47
48    /// Set the model
49    pub fn with_model(mut self, model: impl Into<String>) -> Self {
50        self.model = Some(model.into());
51        self
52    }
53
54    /// Set allowed tools
55    pub fn with_tools(mut self, tools: Vec<String>) -> Self {
56        self.tool_allowlist = tools;
57        self
58    }
59}
60
61/// Control request from Claude to the client
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
63#[serde(tag = "type", rename_all = "snake_case")]
64pub enum ControlRequest {
65    /// Permission check for tool use
66    #[serde(rename = "permission_check")]
67    PermissionCheck(ToolPermissionRequest),
68
69    /// Hook event
70    #[serde(rename = "hook")]
71    Hook {
72        /// The name of the hook event (e.g., "pre_tool_use")
73        event_type: String,
74        /// The data payload for the hook event
75        data: serde_json::Value,
76    },
77
78    /// Permission mode change request
79    #[serde(rename = "permission_mode")]
80    PermissionModeChange {
81        /// The new permission mode to apply
82        mode: PermissionMode,
83    },
84
85    /// Interrupt request
86    #[serde(rename = "interrupt")]
87    Interrupt,
88}
89
90/// Permission check request for tool execution
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
92pub struct ToolPermissionRequest {
93    /// Name of the tool
94    pub tool: String,
95
96    /// Input parameters for the tool
97    pub input: serde_json::Value,
98
99    /// CLI-provided suggestions
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub cli_suggestion: Option<String>,
102}
103
104/// Response to a control request
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
106pub struct ControlResponse {
107    /// Unique identifier for this control request
108    pub request_id: String,
109
110    /// Whether the action was approved
111    pub approved: bool,
112
113    /// Modified input if action was modified
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub modified_input: Option<serde_json::Value>,
116
117    /// Reason if action was denied
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub reason: Option<String>,
120}
121
122/// Permission response for tool execution
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
124pub struct PermissionResponse {
125    /// Whether the tool use is allowed
126    pub allow: bool,
127
128    /// Modified input if needed
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub modified_input: Option<serde_json::Value>,
131
132    /// Permission request suggestion
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub permission_request_suggestion: Option<String>,
135}
136
137impl PermissionResponse {
138    /// Create an allow response
139    pub fn allow() -> Self {
140        Self {
141            allow: true,
142            modified_input: None,
143            permission_request_suggestion: None,
144        }
145    }
146
147    /// Create a deny response
148    pub fn deny() -> Self {
149        Self {
150            allow: false,
151            modified_input: None,
152            permission_request_suggestion: None,
153        }
154    }
155}
156
157/// Hook event types
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
159#[serde(tag = "type", rename_all = "snake_case")]
160pub enum HookEvent {
161    /// Fired before a tool is executed.
162    #[serde(rename = "pre_tool_use")]
163    PreToolUse {
164        /// Data about the tool being used.
165        tool: ToolHookData,
166    },
167
168    /// Fired after a tool is executed.
169    #[serde(rename = "post_tool_use")]
170    PostToolUse {
171        /// Data about the tool that was used.
172        tool: ToolHookData,
173        /// The result of the tool execution.
174        result: ToolResultHookData,
175    },
176
177    /// Fired when the user submits a prompt.
178    #[serde(rename = "user_prompt_submit")]
179    UserPromptSubmit {
180        /// The content of the user's prompt.
181        prompt: String,
182    },
183
184    /// Fired when the main agent session stops.
185    #[serde(rename = "stop")]
186    Stop,
187
188    /// Fired when a subagent session stops.
189    #[serde(rename = "subagent_stop")]
190    SubagentStop,
191
192    /// Fired before the conversation transcript is compacted.
193    #[serde(rename = "pre_compact")]
194    PreCompact,
195}
196
197/// Tool data for hooks
198#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
199pub struct ToolHookData {
200    /// Tool ID
201    pub id: String,
202
203    /// Tool name
204    pub name: String,
205
206    /// Tool input
207    pub input: serde_json::Value,
208}
209
210/// Tool result data for hooks
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
212pub struct ToolResultHookData {
213    /// Tool use ID
214    pub tool_use_id: String,
215
216    /// Result content
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub content: Option<String>,
219
220    /// Whether the result is an error
221    #[serde(default)]
222    pub is_error: bool,
223}
224
225/// Response to a hook event
226#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
227pub struct HookResponse {
228    /// Whether to continue execution
229    pub continue_: bool,
230
231    /// Modified inputs if needed
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub modified_inputs: Option<serde_json::Value>,
234
235    /// Context to inject
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub context: Option<String>,
238
239    /// Whether to hide from transcript
240    #[serde(default)]
241    pub hide_from_transcript: bool,
242}
243
244impl HookResponse {
245    /// Create a continue response
246    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    /// Create a stop response
256    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/// Permission mode for agent sessions
267#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
268#[serde(rename_all = "snake_case")]
269pub enum PermissionMode {
270    /// Ask for permission for each tool (default)
271    Default,
272
273    /// Accept edits automatically
274    AcceptEdits,
275
276    /// Bypass all permission checks
277    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}