Skip to main content

agent_base/types/
message.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
4pub enum MessageRole {
5    System,
6    User,
7    Assistant,
8    Tool,
9}
10
11impl MessageRole {
12    pub fn as_str(&self) -> &'static str {
13        match self {
14            MessageRole::System => "system",
15            MessageRole::User => "user",
16            MessageRole::Assistant => "assistant",
17            MessageRole::Tool => "tool",
18        }
19    }
20
21    pub fn as_api_role(&self) -> &'static str {
22        self.as_str()
23    }
24}
25
26#[derive(Clone, Debug, Serialize, Deserialize)]
27pub struct Message {
28    pub role: MessageRole,
29    pub content: String,
30}
31
32#[derive(Clone, Debug, Serialize, Deserialize)]
33pub enum ChatMessage {
34    System {
35        content: String,
36        /// 临时消息:turn 结束后从内存清理,持久化时跳过。
37        #[serde(default, skip_serializing)]
38        ephemeral: bool,
39    },
40    User {
41        content: String,
42        #[serde(default, skip_serializing_if = "Vec::is_empty")]
43        images: Vec<ImageAttachment>,
44        /// 临时消息:turn 结束后从内存清理,持久化时跳过。
45        #[serde(default, skip_serializing)]
46        ephemeral: bool,
47    },
48    Assistant {
49        content: Option<String>,
50        reasoning_content: Option<String>,
51        tool_calls: Option<Vec<ToolCallMessage>>,
52    },
53    Tool {
54        tool_call_id: String,
55        content: String,
56    },
57}
58
59#[derive(Clone, Debug, Serialize, Deserialize)]
60pub struct ToolCallMessage {
61    pub id: String,
62    pub name: String,
63    pub arguments: String,
64}
65
66#[derive(Clone, Debug, Serialize, Deserialize)]
67pub enum ImageAttachment {
68    Url {
69        url: String,
70        #[serde(default, skip_serializing_if = "Option::is_none")]
71        detail: Option<ImageDetail>,
72    },
73    Base64 {
74        data: String,
75        #[serde(default, skip_serializing_if = "Option::is_none")]
76        media_type: Option<String>,
77        #[serde(default, skip_serializing_if = "Option::is_none")]
78        detail: Option<ImageDetail>,
79    },
80}
81
82#[derive(Clone, Debug, Serialize, Deserialize)]
83pub enum ImageDetail {
84    Low,
85    High,
86    Auto,
87}
88
89impl ChatMessage {
90    pub fn system(content: impl Into<String>) -> Self {
91        Self::System {
92            content: content.into(),
93            ephemeral: false,
94        }
95    }
96
97    /// 创建临时 system 消息:turn 结束后自动清理,不持久化。
98    pub fn system_ephemeral(content: impl Into<String>) -> Self {
99        Self::System {
100            content: content.into(),
101            ephemeral: true,
102        }
103    }
104
105    pub fn user(content: impl Into<String>) -> Self {
106        Self::User {
107            content: content.into(),
108            images: Vec::new(),
109            ephemeral: false,
110        }
111    }
112
113    /// 创建临时 user 消息:turn 结束后自动清理,不持久化。
114    pub fn user_ephemeral(content: impl Into<String>) -> Self {
115        Self::User {
116            content: content.into(),
117            images: Vec::new(),
118            ephemeral: true,
119        }
120    }
121
122    pub fn user_with_images(content: impl Into<String>, images: Vec<ImageAttachment>) -> Self {
123        Self::User {
124            content: content.into(),
125            images,
126            ephemeral: false,
127        }
128    }
129
130    /// 是否为临时消息(turn 结束后自动清理,不持久化)。
131    pub fn is_ephemeral(&self) -> bool {
132        match self {
133            Self::System { ephemeral, .. } => *ephemeral,
134            Self::User { ephemeral, .. } => *ephemeral,
135            _ => false,
136        }
137    }
138
139    pub fn assistant(content: impl Into<String>) -> Self {
140        Self::Assistant {
141            content: Some(content.into()),
142            reasoning_content: None,
143            tool_calls: None,
144        }
145    }
146
147    pub fn assistant_with_reasoning(
148        content: impl Into<String>,
149        reasoning: impl Into<String>,
150    ) -> Self {
151        Self::Assistant {
152            content: Some(content.into()),
153            reasoning_content: Some(reasoning.into()),
154            tool_calls: None,
155        }
156    }
157
158    pub fn assistant_tool_call(
159        tool_call_id: impl Into<String>,
160        tool_name: impl Into<String>,
161        arguments: impl Into<String>,
162    ) -> Self {
163        Self::Assistant {
164            content: None,
165            reasoning_content: None,
166            tool_calls: Some(vec![ToolCallMessage {
167                id: tool_call_id.into(),
168                name: tool_name.into(),
169                arguments: arguments.into(),
170            }]),
171        }
172    }
173
174    pub fn tool(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
175        Self::Tool {
176            tool_call_id: tool_call_id.into(),
177            content: content.into(),
178        }
179    }
180}
181
182impl From<&ChatMessage> for Message {
183    fn from(cm: &ChatMessage) -> Self {
184        match cm {
185            ChatMessage::System { content, .. } => Message {
186                role: MessageRole::System,
187                content: content.clone(),
188            },
189            ChatMessage::User { content, .. } => Message {
190                role: MessageRole::User,
191                content: content.clone(),
192            },
193            ChatMessage::Assistant { content, .. } => Message {
194                role: MessageRole::Assistant,
195                content: content.clone().unwrap_or_default(),
196            },
197            ChatMessage::Tool { content, .. } => Message {
198                role: MessageRole::Tool,
199                content: content.clone(),
200            },
201        }
202    }
203}