Skip to main content

deck_core/
message.rs

1//! Core message / session types that the orchestrator and TUI agree on.
2
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
7#[serde(transparent)]
8pub struct SessionId(pub Uuid);
9
10impl SessionId {
11    #[must_use]
12    pub fn new() -> Self {
13        Self(Uuid::new_v4())
14    }
15}
16
17impl Default for SessionId {
18    fn default() -> Self {
19        Self::new()
20    }
21}
22
23impl std::fmt::Display for SessionId {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.0)
26    }
27}
28
29#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
30#[serde(rename_all = "lowercase")]
31pub enum Role {
32    System,
33    User,
34    Assistant,
35    Tool,
36}
37
38impl Role {
39    /// Canonical wire string. Single source of truth for any code path
40    /// that talks to an LLM HTTP API or persists messages.
41    #[must_use]
42    pub const fn as_wire_str(self) -> &'static str {
43        match self {
44            Self::System => "system",
45            Self::User => "user",
46            Self::Assistant => "assistant",
47            Self::Tool => "tool",
48        }
49    }
50
51    /// Parse the canonical wire string back into a `Role`. Returns
52    /// `None` for unknown values — callers must decide whether to error
53    /// or default; we deliberately do NOT silently coerce.
54    #[must_use]
55    pub fn from_wire_str(s: &str) -> Option<Self> {
56        Some(match s {
57            "system" => Self::System,
58            "user" => Self::User,
59            "assistant" => Self::Assistant,
60            "tool" => Self::Tool,
61            _ => return None,
62        })
63    }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct Message {
68    pub role: Role,
69    pub content: String,
70    #[serde(skip_serializing_if = "Vec::is_empty", default)]
71    pub tool_calls: Vec<ToolCall>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct ToolCall {
76    pub id: String,
77    pub server: String,
78    pub tool: String,
79    pub arguments: serde_json::Value,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct ToolResult {
84    pub call_id: String,
85    pub content: serde_json::Value,
86    #[serde(default)]
87    pub is_error: bool,
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn session_id_is_unique() {
96        let a = SessionId::new();
97        let b = SessionId::new();
98        assert_ne!(a, b);
99    }
100
101    #[test]
102    fn message_serde_roundtrip() {
103        let msg = Message {
104            role: Role::Assistant,
105            content: "hello".into(),
106            tool_calls: vec![],
107        };
108        let json = serde_json::to_string(&msg).unwrap();
109        let back: Message = serde_json::from_str(&json).unwrap();
110        assert!(matches!(back.role, Role::Assistant));
111        assert_eq!(back.content, "hello");
112    }
113}