1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct AcpMessage {
10 pub(crate) id: String,
12
13 #[serde(rename = "type")]
15 message_type: MessageType,
16
17 sender: String,
19
20 recipient: String,
22
23 pub(crate) content: MessageContent,
25
26 timestamp: String,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 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 action: String,
66
67 args: Value,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 timeout_secs: Option<u64>,
73
74 #[serde(default)]
76 pub(crate) sync: bool,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct AcpResponse {
82 status: ResponseStatus,
84
85 #[serde(skip_serializing_if = "Option::is_none")]
87 result: Option<Value>,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
91 error: Option<ErrorDetails>,
92
93 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 code: String,
112
113 message: String,
115
116 #[serde(skip_serializing_if = "Option::is_none")]
118 details: Option<Value>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct ErrorDetails {
124 code: String,
126
127 message: String,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
132 context: Option<Value>,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct NotificationPayload {
138 event: String,
140
141 data: Value,
143}
144
145impl AcpMessage {
146 pub(crate) 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 { action, args, timeout_secs: None, sync: true }),
154 timestamp: chrono::Utc::now().to_rfc3339(),
155 correlation_id: None,
156 }
157 }
158
159 pub fn response(sender: String, recipient: String, result: Value, correlation_id: String) -> Self {
161 Self {
162 id: Uuid::new_v4().to_string(),
163 message_type: MessageType::Response,
164 sender,
165 recipient,
166 content: MessageContent::Response(AcpResponse {
167 status: ResponseStatus::Success,
168 result: Some(result),
169 error: None,
170 execution_time_ms: 0,
171 }),
172 timestamp: chrono::Utc::now().to_rfc3339(),
173 correlation_id: Some(correlation_id),
174 }
175 }
176
177 pub fn error_response(
179 sender: String,
180 recipient: String,
181 code: String,
182 message: String,
183 correlation_id: String,
184 ) -> Self {
185 Self {
186 id: Uuid::new_v4().to_string(),
187 message_type: MessageType::Error,
188 sender,
189 recipient,
190 content: MessageContent::Error(ErrorPayload { code, message, details: None }),
191 timestamp: chrono::Utc::now().to_rfc3339(),
192 correlation_id: Some(correlation_id),
193 }
194 }
195
196 fn to_json(&self) -> anyhow::Result<String> {
198 Ok(serde_json::to_string(self)?)
199 }
200
201 fn from_json(json: &str) -> anyhow::Result<Self> {
203 Ok(serde_json::from_str(json)?)
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use serde_json::json;
211
212 #[test]
213 fn test_message_creation() {
214 let msg = AcpMessage::request(
215 "agent-1".to_string(),
216 "agent-2".to_string(),
217 "execute_tool".to_string(),
218 json!({"tool": "bash", "command": "ls"}),
219 );
220
221 assert_eq!(msg.message_type, MessageType::Request);
222 assert_eq!(msg.sender, "agent-1");
223 assert_eq!(msg.recipient, "agent-2");
224 }
225
226 #[test]
227 fn test_message_serialization() {
228 let msg = AcpMessage::request("agent-1".to_string(), "agent-2".to_string(), "test".to_string(), json!({}));
229
230 let json = msg.to_json().unwrap();
231 let restored = AcpMessage::from_json(&json).unwrap();
232
233 assert_eq!(msg.id, restored.id);
234 assert_eq!(msg.sender, restored.sender);
235 }
236}