1use serde::{Deserialize, Serialize};
2
3#[derive(Clone, Debug, Serialize, Deserialize)]
7pub struct Message {
8 pub id: String,
10 pub role: Role,
12 pub content: Content,
14 pub tool_calls: Vec<ToolCall>,
16 pub tool_call_id: Option<String>,
18 pub name: Option<String>,
20 pub usage: Option<TokenUsage>,
22}
23
24#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
26pub enum Role {
27 System,
29 Human,
31 Ai,
33 Tool,
35}
36
37#[derive(Clone, Debug, Serialize, Deserialize)]
39pub enum Content {
40 Text(String),
42 MultiPart(Vec<ContentPart>),
44}
45
46#[derive(Clone, Debug, Serialize, Deserialize)]
48pub enum ContentPart {
49 Text { text: String },
51 Image(ImageData),
53 Thinking {
57 text: String,
58 signature: Option<String>,
59 },
60}
61
62#[derive(Clone, Debug, Serialize, Deserialize)]
64pub struct ImageData {
65 pub media_type: String,
67 pub source: ImageSource,
69}
70
71#[derive(Clone, Debug, Serialize, Deserialize)]
73pub enum ImageSource {
74 Base64(String),
76 Url(String),
78}
79
80#[derive(Clone, Debug, Serialize, Deserialize)]
82pub struct ToolCall {
83 pub id: String,
85 pub name: String,
87 pub arguments: serde_json::Value,
89}
90
91#[derive(Clone, Debug, Default, Serialize, Deserialize)]
93pub struct TokenUsage {
94 pub input_tokens: u64,
96 pub output_tokens: u64,
98 pub total_tokens: u64,
100}
101
102pub const REMOVE_ALL_MESSAGES: &str = "__remove_all__";
106
107#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
122pub struct MessagesState {
123 pub messages: Vec<Message>,
125}
126
127#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
131pub struct MessagesStateUpdate {
132 pub messages: Option<Vec<Message>>,
134}
135
136impl crate::State for MessagesState {
137 type Update = MessagesStateUpdate;
138 type FieldVersions = crate::state::FieldVersions;
139
140 fn apply(&mut self, update: Self::Update) -> crate::FieldsChanged {
141 let mut changed = crate::FieldsChanged(0);
142
143 if let Some(messages) = update.messages {
144 messages_reducer(&mut self.messages, messages);
145 changed.0 |= 1 << 0;
146 }
147
148 changed
149 }
150
151 fn reset_ephemeral(&mut self) {
152 }
154}
155
156impl MessagesState {
157 pub fn try_apply_messages(
168 &mut self,
169 update: MessagesStateUpdate,
170 ) -> Result<crate::FieldsChanged, crate::error::InvalidUpdateError> {
171 Ok(crate::State::apply(self, update))
172 }
173}
174
175pub fn messages_reducer(current: &mut Vec<Message>, incoming: Vec<Message>) {
183 for msg in incoming {
184 if msg.id == REMOVE_ALL_MESSAGES {
185 current.clear();
186 } else if msg.id.starts_with("__remove__:") {
187 let target_id = &msg.id["__remove__:".len()..];
188 current.retain(|m| m.id != target_id);
189 } else if let Some(existing) = current.iter_mut().find(|m| m.id == msg.id) {
190 *existing = msg;
191 } else {
192 current.push(msg);
193 }
194 }
195}
196
197impl Message {
198 pub fn human(content: impl Into<String>) -> Self {
200 Self {
201 id: uuid::Uuid::new_v4().to_string(),
202 role: Role::Human,
203 content: Content::Text(content.into()),
204 tool_calls: vec![],
205 tool_call_id: None,
206 name: None,
207 usage: None,
208 }
209 }
210
211 pub fn ai(content: impl Into<String>) -> Self {
213 Self {
214 id: uuid::Uuid::new_v4().to_string(),
215 role: Role::Ai,
216 content: Content::Text(content.into()),
217 tool_calls: vec![],
218 tool_call_id: None,
219 name: None,
220 usage: None,
221 }
222 }
223
224 pub fn ai_with_tool_calls(content: impl Into<String>, tool_calls: Vec<ToolCall>) -> Self {
226 Self {
227 id: uuid::Uuid::new_v4().to_string(),
228 role: Role::Ai,
229 content: Content::Text(content.into()),
230 tool_calls,
231 tool_call_id: None,
232 name: None,
233 usage: None,
234 }
235 }
236
237 pub fn system(content: impl Into<String>) -> Self {
239 Self {
240 id: uuid::Uuid::new_v4().to_string(),
241 role: Role::System,
242 content: Content::Text(content.into()),
243 tool_calls: vec![],
244 tool_call_id: None,
245 name: None,
246 usage: None,
247 }
248 }
249
250 pub fn tool_result(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
252 Self {
253 id: uuid::Uuid::new_v4().to_string(),
254 role: Role::Tool,
255 content: Content::Text(content.into()),
256 tool_calls: vec![],
257 tool_call_id: Some(tool_call_id.into()),
258 name: None,
259 usage: None,
260 }
261 }
262
263 #[must_use]
265 pub const fn has_tool_calls(&self) -> bool {
266 !self.tool_calls.is_empty()
267 }
268
269 #[must_use]
275 pub fn content_text(&self) -> &str {
276 match &self.content {
277 Content::Text(s) => s,
278 Content::MultiPart(parts) => parts
279 .iter()
280 .find_map(|p| match p {
281 ContentPart::Text { text } => Some(text.as_str()),
282 _ => None,
283 })
284 .unwrap_or(""),
285 }
286 }
287
288 #[must_use]
290 pub fn remove(id: impl Into<String>) -> Self {
291 let id = id.into();
292 Self {
293 id: format!("__remove__:{id}"),
294 role: Role::System,
295 content: Content::Text(String::new()),
296 tool_calls: vec![],
297 tool_call_id: None,
298 name: None,
299 usage: None,
300 }
301 }
302
303 #[must_use]
309 pub fn remove_all() -> Self {
310 Self {
311 id: REMOVE_ALL_MESSAGES.to_string(),
312 role: Role::System,
313 content: Content::Text(String::new()),
314 tool_calls: vec![],
315 tool_call_id: None,
316 name: None,
317 usage: None,
318 }
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325 use crate::state::trait_::State;
326
327 #[test]
328 fn test_messages_state_default() {
329 let state = MessagesState::default();
330 assert!(state.messages.is_empty());
331 }
332
333 #[test]
334 fn test_messages_state_apply() {
335 let mut state = MessagesState::default();
336
337 let update = MessagesStateUpdate {
338 messages: Some(vec![Message::human("Hello")]),
339 };
340
341 let changed = state.apply(update);
342 assert_eq!(state.messages.len(), 1);
343 assert!(!changed.is_empty());
344 assert!(changed.has_field(0));
345 }
346
347 #[test]
348 fn test_messages_state_apply_merge() {
349 let mut state = MessagesState {
350 messages: vec![Message::human("Hello")],
351 };
352
353 let update = MessagesStateUpdate {
354 messages: Some(vec![Message::ai("Hi there!")]),
355 };
356
357 state.apply(update);
358 assert_eq!(state.messages.len(), 2);
359 }
360
361 #[test]
362 fn test_messages_state_apply_none() {
363 let mut state = MessagesState {
364 messages: vec![Message::human("Hello")],
365 };
366
367 let update = MessagesStateUpdate { messages: None };
368
369 let changed = state.apply(update);
370 assert_eq!(state.messages.len(), 1);
371 assert!(changed.is_empty());
372 }
373
374 #[test]
375 fn test_messages_state_reset_ephemeral() {
376 let mut state = MessagesState {
377 messages: vec![Message::human("Hello")],
378 };
379
380 state.reset_ephemeral();
381 assert_eq!(state.messages.len(), 1);
383 }
384
385 #[test]
386 fn test_messages_state_serialization() {
387 let state = MessagesState {
388 messages: vec![Message::human("Hello")],
389 };
390
391 let json = serde_json::to_string(&state).unwrap();
392 let deserialized: MessagesState = serde_json::from_str(&json).unwrap();
393
394 assert_eq!(deserialized.messages.len(), 1);
395 assert_eq!(deserialized.messages[0].role, Role::Human);
396 }
397
398 #[test]
399 fn test_messages_state_update_serialization() {
400 let update = MessagesStateUpdate {
401 messages: Some(vec![Message::ai("Hi!")]),
402 };
403
404 let json = serde_json::to_string(&update).unwrap();
405 let deserialized: MessagesStateUpdate = serde_json::from_str(&json).unwrap();
406
407 assert!(deserialized.messages.is_some());
408 assert_eq!(deserialized.messages.unwrap().len(), 1);
409 }
410}
411
412