1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct AcpMessage {
10 pub id: String,
12
13 #[serde(rename = "type")]
15 pub message_type: MessageType,
16
17 pub sender: String,
19
20 pub recipient: String,
22
23 pub content: MessageContent,
25
26 pub timestamp: String,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub correlation_id: Option<String>,
32}
33
34#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
36#[serde(rename_all = "lowercase")]
37pub enum MessageType {
38 Request,
39 Response,
40 Error,
41 Notification,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(untagged)]
47pub enum MessageContent {
48 Request(AcpRequest),
50
51 Response(AcpResponse),
53
54 Error(ErrorPayload),
56
57 Notification(NotificationPayload),
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct AcpRequest {
64 pub action: String,
66
67 pub args: Value,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub timeout_secs: Option<u64>,
73
74 #[serde(default)]
76 pub sync: bool,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct AcpResponse {
82 pub status: ResponseStatus,
84
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub result: Option<Value>,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub error: Option<ErrorDetails>,
92
93 pub execution_time_ms: u64,
95}
96
97#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
99#[serde(rename_all = "lowercase")]
100pub enum ResponseStatus {
101 Success,
102 Failed,
103 Timeout,
104 Partial,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct ErrorPayload {
110 pub code: String,
112
113 pub message: String,
115
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub details: Option<Value>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct ErrorDetails {
124 pub code: String,
126
127 pub message: String,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub context: Option<Value>,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct NotificationPayload {
138 pub event: String,
140
141 pub data: Value,
143}
144
145impl AcpMessage {
146 pub fn request(sender: String, recipient: String, action: String, args: Value) -> Self {
148 Self {
149 id: Uuid::new_v4().to_string(),
150 message_type: MessageType::Request,
151 sender,
152 recipient,
153 content: MessageContent::Request(AcpRequest {
154 action,
155 args,
156 timeout_secs: None,
157 sync: true,
158 }),
159 timestamp: chrono::Utc::now().to_rfc3339(),
160 correlation_id: None,
161 }
162 }
163
164 pub fn response(
166 sender: String,
167 recipient: String,
168 result: Value,
169 correlation_id: String,
170 ) -> Self {
171 Self {
172 id: Uuid::new_v4().to_string(),
173 message_type: MessageType::Response,
174 sender,
175 recipient,
176 content: MessageContent::Response(AcpResponse {
177 status: ResponseStatus::Success,
178 result: Some(result),
179 error: None,
180 execution_time_ms: 0,
181 }),
182 timestamp: chrono::Utc::now().to_rfc3339(),
183 correlation_id: Some(correlation_id),
184 }
185 }
186
187 pub fn error_response(
189 sender: String,
190 recipient: String,
191 code: String,
192 message: String,
193 correlation_id: String,
194 ) -> Self {
195 Self {
196 id: Uuid::new_v4().to_string(),
197 message_type: MessageType::Error,
198 sender,
199 recipient,
200 content: MessageContent::Error(ErrorPayload {
201 code,
202 message,
203 details: None,
204 }),
205 timestamp: chrono::Utc::now().to_rfc3339(),
206 correlation_id: Some(correlation_id),
207 }
208 }
209
210 pub fn to_json(&self) -> anyhow::Result<String> {
212 Ok(serde_json::to_string(self)?)
213 }
214
215 pub fn from_json(json: &str) -> anyhow::Result<Self> {
217 Ok(serde_json::from_str(json)?)
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use serde_json::json;
225
226 #[test]
227 fn test_message_creation() {
228 let msg = AcpMessage::request(
229 "agent-1".to_string(),
230 "agent-2".to_string(),
231 "execute_tool".to_string(),
232 json!({"tool": "bash", "command": "ls"}),
233 );
234
235 assert_eq!(msg.message_type, MessageType::Request);
236 assert_eq!(msg.sender, "agent-1");
237 assert_eq!(msg.recipient, "agent-2");
238 }
239
240 #[test]
241 fn test_message_serialization() {
242 let msg = AcpMessage::request(
243 "agent-1".to_string(),
244 "agent-2".to_string(),
245 "test".to_string(),
246 json!({}),
247 );
248
249 let json = msg.to_json().unwrap();
250 let restored = AcpMessage::from_json(&json).unwrap();
251
252 assert_eq!(msg.id, restored.id);
253 assert_eq!(msg.sender, restored.sender);
254 }
255}