use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AcpMessage {
pub id: String,
#[serde(rename = "type")]
pub message_type: MessageType,
pub sender: String,
pub recipient: String,
pub content: MessageContent,
pub timestamp: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub correlation_id: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum MessageType {
Request,
Response,
Error,
Notification,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MessageContent {
Request(AcpRequest),
Response(AcpResponse),
Error(ErrorPayload),
Notification(NotificationPayload),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AcpRequest {
pub action: String,
pub args: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout_secs: Option<u64>,
#[serde(default)]
pub sync: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AcpResponse {
pub status: ResponseStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ErrorDetails>,
pub execution_time_ms: u64,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ResponseStatus {
Success,
Failed,
Timeout,
Partial,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorPayload {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorDetails {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationPayload {
pub event: String,
pub data: Value,
}
impl AcpMessage {
pub fn request(sender: String, recipient: String, action: String, args: Value) -> Self {
Self {
id: Uuid::new_v4().to_string(),
message_type: MessageType::Request,
sender,
recipient,
content: MessageContent::Request(AcpRequest {
action,
args,
timeout_secs: None,
sync: true,
}),
timestamp: chrono::Utc::now().to_rfc3339(),
correlation_id: None,
}
}
pub fn response(
sender: String,
recipient: String,
result: Value,
correlation_id: String,
) -> Self {
Self {
id: Uuid::new_v4().to_string(),
message_type: MessageType::Response,
sender,
recipient,
content: MessageContent::Response(AcpResponse {
status: ResponseStatus::Success,
result: Some(result),
error: None,
execution_time_ms: 0,
}),
timestamp: chrono::Utc::now().to_rfc3339(),
correlation_id: Some(correlation_id),
}
}
pub fn error_response(
sender: String,
recipient: String,
code: String,
message: String,
correlation_id: String,
) -> Self {
Self {
id: Uuid::new_v4().to_string(),
message_type: MessageType::Error,
sender,
recipient,
content: MessageContent::Error(ErrorPayload {
code,
message,
details: None,
}),
timestamp: chrono::Utc::now().to_rfc3339(),
correlation_id: Some(correlation_id),
}
}
pub fn to_json(&self) -> anyhow::Result<String> {
Ok(serde_json::to_string(self)?)
}
pub fn from_json(json: &str) -> anyhow::Result<Self> {
Ok(serde_json::from_str(json)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_message_creation() {
let msg = AcpMessage::request(
"agent-1".to_string(),
"agent-2".to_string(),
"execute_tool".to_string(),
json!({"tool": "bash", "command": "ls"}),
);
assert_eq!(msg.message_type, MessageType::Request);
assert_eq!(msg.sender, "agent-1");
assert_eq!(msg.recipient, "agent-2");
}
#[test]
fn test_message_serialization() {
let msg = AcpMessage::request(
"agent-1".to_string(),
"agent-2".to_string(),
"test".to_string(),
json!({}),
);
let json = msg.to_json().unwrap();
let restored = AcpMessage::from_json(&json).unwrap();
assert_eq!(msg.id, restored.id);
assert_eq!(msg.sender, restored.sender);
}
}