Skip to main content

ras_llm/domain/
message.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
4#[serde(tag = "role", rename_all = "lowercase")]
5pub enum ChatMessage {
6    System(SystemMessage),
7    User(UserMessage),
8    Assistant(AssistantMessage),
9    Tool(ToolResultMessage),
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SystemMessage {
14    pub content: String,
15    #[serde(default)]
16    pub cache: bool,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct UserMessage {
21    pub content: Vec<ContentPart>,
22    #[serde(default)]
23    pub cache: bool,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct AssistantMessage {
28    pub content: Option<String>,
29    #[serde(default)]
30    pub tool_calls: Vec<ToolCall>,
31    #[serde(default)]
32    pub cache: bool,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ToolResultMessage {
37    pub tool_call_id: String,
38    pub content: String,
39    #[serde(default)]
40    pub is_error: bool,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44#[serde(tag = "type", rename_all = "snake_case")]
45pub enum ContentPart {
46    Text { text: String },
47    ImageBase64 { media_type: String, data: String },
48    ImageUrl { url: String },
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct ToolCall {
53    pub id: String,
54    pub name: String,
55    pub arguments: serde_json::Value,
56}
57
58impl ChatMessage {
59    #[must_use]
60    pub fn system(content: impl Into<String>) -> Self {
61        Self::System(SystemMessage {
62            content: content.into(),
63            cache: false,
64        })
65    }
66
67    /// A system message marked for prompt caching. Use for a large, STABLE
68    /// prefix (a task/system prompt re-sent every step): providers that support
69    /// it (Anthropic via OpenRouter) read the prefix from cache instead of
70    /// re-prefilling it, cutting per-step latency and cost with no output change.
71    #[must_use]
72    pub fn system_cached(content: impl Into<String>) -> Self {
73        Self::System(SystemMessage {
74            content: content.into(),
75            cache: true,
76        })
77    }
78
79    #[must_use]
80    pub fn user_text(text: impl Into<String>) -> Self {
81        Self::User(UserMessage {
82            content: vec![ContentPart::Text { text: text.into() }],
83            cache: false,
84        })
85    }
86
87    #[must_use]
88    pub fn user_parts(parts: Vec<ContentPart>) -> Self {
89        Self::User(UserMessage {
90            content: parts,
91            cache: false,
92        })
93    }
94
95    #[must_use]
96    pub fn assistant_text(text: impl Into<String>) -> Self {
97        Self::Assistant(AssistantMessage {
98            content: Some(text.into()),
99            tool_calls: Vec::new(),
100            cache: false,
101        })
102    }
103}