Skip to main content

recursive/
message.rs

1//! Chat message primitive.
2//!
3//! A `Message` mirrors the wire format that most chat completion APIs use,
4//! but stays provider-agnostic. Providers translate to/from their own shape
5//! in their adapter.
6
7use serde::{Deserialize, Serialize};
8
9use crate::llm::ToolCall;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum Role {
14    System,
15    User,
16    Assistant,
17    Tool,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct Message {
22    pub role: Role,
23    pub content: String,
24    #[serde(default, skip_serializing_if = "Vec::is_empty")]
25    pub tool_calls: Vec<ToolCall>,
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub tool_call_id: Option<String>,
28    /// DeepSeek reasoning/thinking content. Must be echoed back to the API
29    /// when present, otherwise the API returns a 400 error.
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub reasoning_content: Option<String>,
32}
33
34impl Message {
35    pub fn system(content: impl Into<String>) -> Self {
36        Self {
37            role: Role::System,
38            content: content.into(),
39            tool_calls: Vec::new(),
40            tool_call_id: None,
41            reasoning_content: None,
42        }
43    }
44
45    pub fn user(content: impl Into<String>) -> Self {
46        Self {
47            role: Role::User,
48            content: content.into(),
49            tool_calls: Vec::new(),
50            tool_call_id: None,
51            reasoning_content: None,
52        }
53    }
54
55    pub fn assistant(content: impl Into<String>) -> Self {
56        Self {
57            role: Role::Assistant,
58            content: content.into(),
59            tool_calls: Vec::new(),
60            tool_call_id: None,
61            reasoning_content: None,
62        }
63    }
64
65    pub fn assistant_with_tool_calls(
66        content: impl Into<String>,
67        tool_calls: Vec<ToolCall>,
68    ) -> Self {
69        Self {
70            role: Role::Assistant,
71            content: content.into(),
72            tool_calls,
73            tool_call_id: None,
74            reasoning_content: None,
75        }
76    }
77
78    pub fn tool_result(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
79        Self {
80            role: Role::Tool,
81            content: content.into(),
82            tool_calls: Vec::new(),
83            tool_call_id: Some(tool_call_id.into()),
84            reasoning_content: None,
85        }
86    }
87}