//! Message types shared between producer and worker. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RoomMessageEnvelope { pub id: Uuid, pub dedup_key: Option, pub room_id: Uuid, pub sender_type: String, pub sender_id: Option, /// AI model ID — set when sender_type = "ai", used for display name lookups. pub model_id: Option, pub thread_id: Option, pub in_reply_to: Option, pub content: String, pub content_type: String, /// Accumulated AI reasoning/thinking text. #[serde(skip_serializing_if = "Option::is_none")] pub thinking_content: Option, pub send_at: DateTime, pub seq: i64, /// Pre-resolved display name for the sender (e.g. AI model name). #[serde(skip_serializing_if = "Option::is_none")] pub display_name: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RoomMessageEvent { pub id: Uuid, pub room_id: Uuid, pub sender_type: String, pub sender_id: Option, pub thread_id: Option, pub in_reply_to: Option, pub content: String, pub content_type: String, /// Accumulated AI reasoning/thinking text. #[serde(skip_serializing_if = "Option::is_none")] pub thinking_content: Option, pub send_at: DateTime, pub seq: i64, pub display_name: Option, /// Present when this event carries reaction updates for the message. #[serde(skip_serializing_if = "Option::is_none")] pub reactions: Option>, /// Target message ID for reaction update events. #[serde(skip_serializing_if = "Option::is_none")] pub message_id: Option, } /// Typing indicator event — broadcast to all room members. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TypingEvent { pub room_id: Uuid, pub user_id: Uuid, pub username: String, pub avatar_url: Option, /// "start" or "stop" pub action: String, /// Sender type: "user" or "ai". Defaults to "user" if absent. #[serde(skip_serializing_if = "Option::is_none")] pub sender_type: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ReactionGroup { pub emoji: String, pub count: i32, pub reacted_by_me: bool, /// Stored as strings (UUIDs) to match the frontend's `users: string[]` type. pub users: Vec, } impl From for RoomMessageEvent { fn from(e: RoomMessageEnvelope) -> Self { Self { id: e.id, room_id: e.room_id, sender_type: e.sender_type, sender_id: e.sender_id, thread_id: e.thread_id, in_reply_to: e.in_reply_to, content: e.content, content_type: e.content_type, thinking_content: e.thinking_content, send_at: e.send_at, seq: e.seq, display_name: e.display_name, reactions: None, message_id: None, } } } impl From for RoomMessageEnvelope { fn from(e: RoomMessageEvent) -> Self { Self { id: e.id, dedup_key: None, room_id: e.room_id, sender_type: e.sender_type, sender_id: e.sender_id, model_id: None, thread_id: e.thread_id, in_reply_to: e.in_reply_to, content: e.content, content_type: e.content_type, thinking_content: e.thinking_content, send_at: e.send_at, seq: e.seq, display_name: e.display_name, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProjectRoomEvent { pub event_type: String, pub project_id: Uuid, pub room_id: Option, pub category_id: Option, pub message_id: Option, pub seq: Option, pub timestamp: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RoomMessageStreamChunkEvent { pub message_id: Uuid, pub room_id: Uuid, /// Monotonically increasing sequence number for ordering within this stream. #[serde(default)] pub seq: u64, pub content: String, pub done: bool, pub error: Option, /// Human-readable AI model name (e.g. "Claude 3.5 Sonnet") for display. pub display_name: Option, /// What kind of content this chunk contains: "thinking", "answer", "tool_call", "tool_result". pub chunk_type: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EmailEnvelope { pub id: Uuid, pub to: String, pub subject: String, pub body: String, pub created_at: DateTime, } /// Agent task event published via NATS core to notify WebSocket clients. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AgentTaskEvent { /// Task ID pub task_id: i64, /// Project this task belongs to. pub project_id: Uuid, /// Parent task ID (null for root tasks). pub parent_id: Option, /// Event type: started | progress | done | failed | child_done pub event: String, /// Human-readable progress/status text. pub message: Option, /// Task output (only on done event). pub output: Option, /// Error message (only on failed event). pub error: Option, /// Current status. pub status: String, /// Timestamp. pub timestamp: DateTime, } /// Chat message event — broadcast via NATS JetStream for persistence + multi-viewer support. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatMessageEvent { pub message_id: Uuid, pub conversation_id: Uuid, pub project_id: Option, pub sender_id: Uuid, pub role: String, pub content: String, pub model: Option, pub input_tokens: Option, pub output_tokens: Option, pub timestamp: DateTime, } /// Chat stream chunk event — broadcast via NATS JetStream for real-time multi-viewer streaming. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatStreamChunkEvent { pub conversation_id: Uuid, pub message_id: Uuid, pub seq: u64, pub content: String, pub done: bool, pub error: Option, pub chunk_type: Option, pub model_name: Option, } /// Sub-agent stream chunk event — published to dedicated NATS subject `chat.subagent.chunk.{conversation_id}.{children_id}`. /// Frontend subscribes to this subject via the sub-agent SSE endpoint using children_id. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SubAgentStreamChunkEvent { pub conversation_id: Uuid, pub children_id: String, pub seq: u64, pub content: String, pub done: bool, pub error: Option, pub chunk_type: Option, pub role: String, pub task: String, } /// Sub-agent session record — persisted after sub-agent completes. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SubAgentSessionRecord { pub conversation_id: Uuid, pub children_id: String, pub role: String, pub task: String, pub output: String, pub input_tokens: i64, pub output_tokens: i64, pub created_at: DateTime, }