use serde::{Deserialize, Serialize};
use super::cursor::Cursor;
use super::ids::{AgentId, RoomId};
use super::model::{AgentCard, MessageMeta, Room, RoomScope};
pub const PROTO_VER: u32 = 1;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "method", content = "params", rename_all = "snake_case")]
pub enum CommsRequest {
Hello {
agent: AgentId,
proto_ver: u32,
#[serde(default)]
remote: Option<String>,
#[serde(default)]
cwd: Option<std::path::PathBuf>,
},
Register {
card: AgentCard,
},
ListAgents {
#[serde(default)]
room: Option<RoomId>,
},
CreateRoom {
room: RoomId,
scope: RoomScope,
#[serde(default)]
title: Option<String>,
},
ListRooms {
#[serde(default)]
remote: Option<String>,
#[serde(default)]
cwd: Option<std::path::PathBuf>,
},
Join {
room: RoomId,
},
Leave {
room: RoomId,
},
Post {
room: RoomId,
subject: String,
#[serde(default)]
tags: Vec<String>,
#[serde(default)]
reply_to: Option<String>,
#[serde(default)]
scope: Vec<String>,
body: Vec<u8>,
},
History {
room: RoomId,
#[serde(default)]
cursor: Option<Cursor>,
#[serde(default)]
limit: Option<u32>,
},
GetBody {
message_id: String,
},
Inbox {
#[serde(default)]
remote: Option<String>,
#[serde(default)]
cwd: Option<std::path::PathBuf>,
#[serde(default)]
cursor: Option<Cursor>,
#[serde(default)]
limit: Option<u32>,
#[serde(default)]
mark_read: bool,
},
AckInbox {
#[serde(default)]
message_ids: Vec<String>,
#[serde(default)]
room: Option<RoomId>,
#[serde(default)]
to_seq: Option<u64>,
},
Subscribe {
room: RoomId,
},
Unsubscribe {
sub: u64,
},
Ping,
Stop,
Status,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "result", content = "data", rename_all = "snake_case")]
pub enum CommsResponse {
Welcome {
proto_ver: u32,
daemon_version: String,
},
Ok,
Agents(Vec<super::model::AgentRecord>),
Room(Room),
Rooms(Vec<Room>),
Posted {
message_id: String,
},
History {
messages: Vec<SeqMeta>,
next_cursor: Option<Cursor>,
},
Inbox {
messages: Vec<SeqMeta>,
unread: u32,
next_cursor: Option<Cursor>,
},
Acked {
acked: u32,
cursors_advanced: Vec<(String, u64)>,
},
Body {
body: Option<Vec<u8>>,
},
Subscribed {
sub: u64,
},
Pong,
Status(StatusReport),
Error {
code: String,
message: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SeqMeta {
pub seq: u64,
pub meta: MessageMeta,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct StatusReport {
pub pid: u32,
pub version: String,
pub proto_ver: u32,
pub uptime_secs: u64,
pub rooms: u32,
pub subscribers: u32,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "notify", content = "data", rename_all = "snake_case")]
pub enum CommsNotification {
Message(MessageMeta),
Shutdown,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CommsOut {
Response(CommsResponse),
Notification(CommsNotification),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_round_trips_through_msgpack() {
let req = CommsRequest::Post {
room: RoomId::parse("room-1").expect("room"),
subject: "hi".to_string(),
tags: vec!["t".to_string()],
reply_to: None,
scope: vec!["src/**".to_string()],
body: b"hello".to_vec(),
};
let bytes = rmp_serde::to_vec_named(&req).expect("encode");
let back: CommsRequest = rmp_serde::from_slice(&bytes).expect("decode");
assert_eq!(req, back);
}
#[test]
fn request_is_json_rpc_shaped() {
let req = CommsRequest::Ping;
let json = serde_json::to_value(&req).expect("json");
assert_eq!(json["method"], "ping");
}
#[test]
fn out_frame_round_trips() {
let out = CommsOut::Notification(CommsNotification::Shutdown);
let bytes = rmp_serde::to_vec_named(&out).expect("encode");
let back: CommsOut = rmp_serde::from_slice(&bytes).expect("decode");
assert_eq!(out, back);
}
}