Skip to main content

agent_io/llm/types/
message.rs

1//! Message types for LLM conversations
2
3use serde::{Deserialize, Serialize};
4
5use super::content::ContentPart;
6use super::tool::ToolCall;
7
8/// User message
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct UserMessage {
11    pub role: String,
12    pub content: Vec<ContentPart>,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub name: Option<String>,
15}
16
17impl UserMessage {
18    pub fn new(content: impl Into<String>) -> Self {
19        Self {
20            role: "user".to_string(),
21            content: vec![ContentPart::text(content)],
22            name: None,
23        }
24    }
25
26    pub fn with_parts(content: Vec<ContentPart>) -> Self {
27        Self {
28            role: "user".to_string(),
29            content,
30            name: None,
31        }
32    }
33
34    pub fn with_name(mut self, name: impl Into<String>) -> Self {
35        self.name = Some(name.into());
36        self
37    }
38}
39
40/// System message
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct SystemMessage {
43    pub role: String,
44    pub content: String,
45}
46
47impl SystemMessage {
48    pub fn new(content: impl Into<String>) -> Self {
49        Self {
50            role: "system".to_string(),
51            content: content.into(),
52        }
53    }
54}
55
56/// Developer message (for o1+ models)
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct DeveloperMessage {
59    pub role: String,
60    pub content: String,
61}
62
63impl DeveloperMessage {
64    pub fn new(content: impl Into<String>) -> Self {
65        Self {
66            role: "developer".to_string(),
67            content: content.into(),
68        }
69    }
70}
71
72/// Assistant message
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct AssistantMessage {
75    pub role: String,
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub content: Option<String>,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub thinking: Option<String>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub redacted_thinking: Option<String>,
82    #[serde(default)]
83    pub tool_calls: Vec<ToolCall>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub refusal: Option<String>,
86}
87
88impl AssistantMessage {
89    pub fn new(content: impl Into<String>) -> Self {
90        Self {
91            role: "assistant".to_string(),
92            content: Some(content.into()),
93            thinking: None,
94            redacted_thinking: None,
95            tool_calls: Vec::new(),
96            refusal: None,
97        }
98    }
99
100    pub fn with_tool_calls(mut self, tool_calls: Vec<ToolCall>) -> Self {
101        self.tool_calls = tool_calls;
102        self
103    }
104
105    pub fn with_thinking(mut self, thinking: impl Into<String>) -> Self {
106        self.thinking = Some(thinking.into());
107        self
108    }
109
110    pub fn is_empty(&self) -> bool {
111        self.content.is_none() && self.thinking.is_none() && self.tool_calls.is_empty()
112    }
113}
114
115/// Tool result message
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct ToolMessage {
118    pub role: String,
119    pub content: String,
120    pub tool_call_id: String,
121    /// Tool name that produced this result
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub tool_name: Option<String>,
124    /// Whether this is an ephemeral message
125    #[serde(default)]
126    pub ephemeral: bool,
127    /// Whether this ephemeral message has been destroyed
128    #[serde(default)]
129    pub destroyed: bool,
130}
131
132impl ToolMessage {
133    pub fn new(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
134        Self {
135            role: "tool".to_string(),
136            content: content.into(),
137            tool_call_id: tool_call_id.into(),
138            tool_name: None,
139            ephemeral: false,
140            destroyed: false,
141        }
142    }
143
144    pub fn with_tool_name(mut self, name: impl Into<String>) -> Self {
145        self.tool_name = Some(name.into());
146        self
147    }
148
149    pub fn with_ephemeral(mut self, ephemeral: bool) -> Self {
150        self.ephemeral = ephemeral;
151        self
152    }
153
154    pub fn destroy(&mut self) {
155        self.destroyed = true;
156        self.content = "<removed to save context>".to_string();
157    }
158}
159
160/// Union type for all messages
161#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(tag = "role")]
163#[serde(rename_all = "lowercase")]
164pub enum Message {
165    User(UserMessage),
166    Assistant(AssistantMessage),
167    System(SystemMessage),
168    Developer(DeveloperMessage),
169    Tool(ToolMessage),
170}
171
172impl Message {
173    pub fn user(content: impl Into<String>) -> Self {
174        Message::User(UserMessage::new(content))
175    }
176
177    pub fn assistant(content: impl Into<String>) -> Self {
178        Message::Assistant(AssistantMessage::new(content))
179    }
180
181    pub fn system(content: impl Into<String>) -> Self {
182        Message::System(SystemMessage::new(content))
183    }
184
185    pub fn tool(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
186        Message::Tool(ToolMessage::new(tool_call_id, content))
187    }
188
189    pub fn role(&self) -> &str {
190        match self {
191            Message::User(_) => "user",
192            Message::Assistant(_) => "assistant",
193            Message::System(_) => "system",
194            Message::Developer(_) => "developer",
195            Message::Tool(_) => "tool",
196        }
197    }
198}
199
200impl From<UserMessage> for Message {
201    fn from(msg: UserMessage) -> Self {
202        Message::User(msg)
203    }
204}
205
206impl From<AssistantMessage> for Message {
207    fn from(msg: AssistantMessage) -> Self {
208        Message::Assistant(msg)
209    }
210}
211
212impl From<SystemMessage> for Message {
213    fn from(msg: SystemMessage) -> Self {
214        Message::System(msg)
215    }
216}
217
218impl From<ToolMessage> for Message {
219    fn from(msg: ToolMessage) -> Self {
220        Message::Tool(msg)
221    }
222}