//! Shared response/DTO shapes for the matrix tool surface. //! //! `hive-matrix-daemon` serves its MCP tools directly over //! streamable-http (see [`crate::mcp`]) — there is no separate bridge //! process and no wire protocol between two binaries any more, so this //! module carries only the handler-facing result type //! ([`DaemonResponse`]) and small DTOs ([`InviteAction`], //! [`RoomUnread`]) shared between [`crate::handlers`] and its callers //! (the MCP tool router, the wake-signal formatter). use serde::{Deserialize, Serialize}; /// Whether to accept or reject a pending invite (`resolve_invite` /// tool). Serialises as `"accept"` / `"reject"` on the wire (kept /// `Serialize`/`Deserialize` for the JSON DTOs handlers build). #[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum InviteAction { /// Accept the invite — join the room (same effect as `join_room`). Accept, /// Reject the invite — decline it and leave the room. Reject, } /// One entry in the `unread_summary` response payload. #[derive(Debug, Serialize, Deserialize)] pub struct RoomUnread { /// Canonical alias (`#name:server`) or room id (`!id:server`). pub label: String, /// Always `1` — an exact unread-message count is no longer tracked /// (see `handlers::room_unread_state`'s doc comment for why); the /// agent reads the room via `read_room` for full context, so only /// "there's something new" plus a one-message preview matters here. pub count: u32, /// Truncated body of the room's latest (unread) event. Present when /// that event is a text-like message; absent for a reaction/state /// event or when the fetch failed. #[serde(skip_serializing_if = "Option::is_none")] pub last_body: Option, /// Sender of the last message (`@user:server`). Present when /// `last_body` is present. #[serde(skip_serializing_if = "Option::is_none")] pub last_sender: Option, } /// Result shape every [`crate::handlers`] function returns: `Ok` /// carries the payload (any JSON; the MCP tool router renders it as /// the tool result string), `Error` carries a human-readable error /// string. Kept as a distinct type (rather than each handler /// returning a bare `String`) so the tool router can uniformly render /// success vs error without every handler duplicating that /// formatting. #[derive(Debug, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum DaemonResponse { Ok { payload: serde_json::Value }, Error { message: String }, } impl DaemonResponse { /// Convenience: build an Ok response from any serializable value. /// Serialisation failure (impossible for the small handler structs /// we use, but the API is generic) surfaces as a structured /// `{"serialise_error": "..."}` payload so callers see WHY the /// data is missing instead of a silent `null`. pub fn ok(payload: &T) -> Self { let payload = serde_json::to_value(payload) .unwrap_or_else(|e| serde_json::json!({ "serialise_error": e.to_string() })); Self::Ok { payload } } /// Convenience: build an Error response from any `Display` value. pub fn error(msg: impl std::fmt::Display) -> Self { Self::Error { message: msg.to_string(), } } }