praxis_persist/models/
db_message.rs1use serde::{Deserialize, Serialize};
2use chrono::{DateTime, Utc};
3use praxis_llm::types::FunctionCall;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct DBMessage {
8 pub id: String,
9 pub thread_id: String,
10 pub user_id: String,
11 pub role: MessageRole,
12 pub message_type: MessageType,
13 pub content: String,
14 pub tool_call_id: Option<String>,
15 pub tool_name: Option<String>,
16 pub arguments: Option<serde_json::Value>,
17 pub reasoning_id: Option<String>,
18 pub created_at: DateTime<Utc>,
19 pub duration_ms: Option<u64>,
20}
21
22impl Default for DBMessage {
23 fn default() -> Self {
24 Self {
25 id: uuid::Uuid::new_v4().to_string(),
26 thread_id: String::new(),
27 user_id: String::new(),
28 role: MessageRole::Assistant,
29 message_type: MessageType::Message,
30 content: String::new(),
31 tool_call_id: None,
32 tool_name: None,
33 arguments: None,
34 reasoning_id: None,
35 created_at: Utc::now(),
36 duration_ms: None,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42#[serde(rename_all = "lowercase")]
43pub enum MessageRole {
44 User,
45 Assistant,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "snake_case")]
50pub enum MessageType {
51 Message,
52 Reasoning,
53 ToolCall,
54 ToolResult,
55}
56
57impl TryFrom<DBMessage> for praxis_llm::Message {
59 type Error = anyhow::Error;
60
61 fn try_from(msg: DBMessage) -> Result<Self, Self::Error> {
62 match (msg.role, msg.message_type) {
63 (MessageRole::User, MessageType::Message) => {
64 Ok(praxis_llm::Message::Human {
65 content: praxis_llm::Content::text(msg.content),
66 name: None,
67 })
68 },
69 (MessageRole::Assistant, MessageType::Message) => {
70 Ok(praxis_llm::Message::AI {
71 content: Some(praxis_llm::Content::text(msg.content)),
72 tool_calls: None,
73 name: None,
74 })
75 },
76 (MessageRole::Assistant, MessageType::ToolCall) => {
77 if let (Some(tool_call_id), Some(tool_name), Some(arguments)) =
79 (msg.tool_call_id, msg.tool_name, msg.arguments) {
80 Ok(praxis_llm::Message::AI {
81 content: None,
82 tool_calls: Some(vec![praxis_llm::ToolCall {
83 id: tool_call_id,
84 tool_type: "function".to_string(),
85 function: FunctionCall {
86 name: tool_name,
87 arguments: serde_json::to_string(&arguments)
88 .unwrap_or_else(|_| "{}".to_string()),
89 },
90 }]),
91 name: None,
92 })
93 } else {
94 Err(anyhow::anyhow!("Invalid tool call message: missing required fields"))
95 }
96 },
97 (_, MessageType::ToolResult) => {
98 if let Some(tool_call_id) = msg.tool_call_id {
100 Ok(praxis_llm::Message::Tool {
101 tool_call_id,
102 content: praxis_llm::Content::text(msg.content),
103 })
104 } else {
105 Err(anyhow::anyhow!("Invalid tool result message: missing tool_call_id"))
106 }
107 },
108 (_, MessageType::Reasoning) => {
110 Err(anyhow::anyhow!("Reasoning messages are not converted to LLM messages"))
111 },
112 _ => {
114 Err(anyhow::anyhow!("Invalid message role/type combination"))
115 },
116 }
117 }
118}
119