1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "lowercase")]
9pub enum Role {
10 System,
12 User,
14 Assistant,
16 Tool,
18}
19
20#[derive(Debug, Clone, PartialEq)]
26pub struct ChatMessage {
27 pub role: Role,
29
30 pub content: Option<String>,
33
34 pub content_parts: Option<Vec<serde_json::Value>>,
39
40 pub tool_calls: Option<Vec<ToolCall>>,
42
43 pub tool_call_id: Option<String>,
45
46 pub name: Option<String>,
48
49 pub metadata: std::collections::BTreeMap<String, String>,
55}
56
57impl serde::Serialize for ChatMessage {
58 fn serialize<S: serde::Serializer>(&self, ser: S) -> std::result::Result<S::Ok, S::Error> {
59 use serde::ser::SerializeMap;
60 let mut m = ser.serialize_map(None)?;
61 m.serialize_entry("role", &self.role)?;
62 if let Some(parts) = &self.content_parts {
64 m.serialize_entry("content", parts)?;
65 } else if let Some(c) = &self.content {
66 m.serialize_entry("content", c)?;
67 }
68 if let Some(tc) = &self.tool_calls {
69 m.serialize_entry("tool_calls", tc)?;
70 }
71 if let Some(id) = &self.tool_call_id {
72 m.serialize_entry("tool_call_id", id)?;
73 }
74 if let Some(n) = &self.name {
75 m.serialize_entry("name", n)?;
76 }
77 m.end()
78 }
79}
80
81impl<'de> serde::Deserialize<'de> for ChatMessage {
82 fn deserialize<D: serde::Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
83 #[derive(Deserialize)]
84 struct Raw {
85 role: Role,
86 #[serde(default)]
87 content: Option<serde_json::Value>,
88 #[serde(default)]
89 tool_calls: Option<Vec<ToolCall>>,
90 #[serde(default)]
91 tool_call_id: Option<String>,
92 #[serde(default)]
93 name: Option<String>,
94 }
95 let raw = Raw::deserialize(de)?;
96 let (content, content_parts) = match raw.content {
98 Some(serde_json::Value::String(s)) => (Some(s), None),
99 Some(serde_json::Value::Array(a)) => (None, Some(a)),
100 Some(serde_json::Value::Null) | None => (None, None),
101 Some(other) => (Some(other.to_string()), None),
102 };
103 Ok(ChatMessage {
104 role: raw.role,
105 content,
106 content_parts,
107 tool_calls: raw.tool_calls,
108 tool_call_id: raw.tool_call_id,
109 name: raw.name,
110 metadata: Default::default(),
111 })
112 }
113}
114
115impl ChatMessage {
116 pub fn system(content: impl Into<String>) -> Self {
118 Self::text(Role::System, content)
119 }
120
121 pub fn user_with_images(text: impl Into<String>, image_urls: &[String]) -> Self {
125 let mut parts = vec![serde_json::json!({"type": "text", "text": text.into()})];
126 for url in image_urls {
127 parts.push(serde_json::json!({"type": "image_url", "image_url": {"url": url}}));
128 }
129 ChatMessage {
130 role: Role::User,
131 content: None,
132 content_parts: Some(parts),
133 tool_calls: None,
134 tool_call_id: None,
135 name: None,
136 metadata: Default::default(),
137 }
138 }
139
140 pub fn user(content: impl Into<String>) -> Self {
142 Self::text(Role::User, content)
143 }
144
145 pub fn assistant(content: impl Into<String>) -> Self {
147 Self::text(Role::Assistant, content)
148 }
149
150 pub fn tool_result(
152 tool_call_id: impl Into<String>,
153 name: impl Into<String>,
154 content: impl Into<String>,
155 ) -> Self {
156 ChatMessage {
157 role: Role::Tool,
158 content: Some(content.into()),
159 content_parts: None,
160 tool_calls: None,
161 tool_call_id: Some(tool_call_id.into()),
162 name: Some(name.into()),
163 metadata: Default::default(),
164 }
165 }
166
167 pub fn tool_result_with_image(
174 tool_call_id: impl Into<String>,
175 name: impl Into<String>,
176 notice: impl Into<String>,
177 data_url: impl Into<String>,
178 ) -> Self {
179 ChatMessage {
180 role: Role::Tool,
181 content: None,
182 content_parts: Some(vec![
183 serde_json::json!({"type": "text", "text": notice.into()}),
184 serde_json::json!({"type": "image_url", "image_url": {"url": data_url.into()}}),
185 ]),
186 tool_calls: None,
187 tool_call_id: Some(tool_call_id.into()),
188 name: Some(name.into()),
189 metadata: Default::default(),
190 }
191 }
192
193 fn text(role: Role, content: impl Into<String>) -> Self {
194 ChatMessage {
195 role,
196 content: Some(content.into()),
197 content_parts: None,
198 tool_calls: None,
199 tool_call_id: None,
200 name: None,
201 metadata: Default::default(),
202 }
203 }
204
205 pub fn tool_calls(&self) -> &[ToolCall] {
207 self.tool_calls.as_deref().unwrap_or(&[])
208 }
209
210 pub fn with_meta(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
212 self.metadata.insert(key.into(), value.into());
213 self
214 }
215
216 pub fn with_metas(mut self, pairs: &[(String, String)]) -> Self {
218 for (k, v) in pairs {
219 self.metadata.insert(k.clone(), v.clone());
220 }
221 self
222 }
223}
224
225#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
227pub struct ToolCall {
228 pub id: String,
230
231 #[serde(rename = "type", default = "default_tool_type")]
233 pub kind: String,
234
235 pub function: FunctionCall,
237}
238
239#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
241pub struct FunctionCall {
242 pub name: String,
244
245 pub arguments: String,
247}
248
249impl FunctionCall {
250 pub fn parsed_arguments(&self) -> serde_json::Result<serde_json::Value> {
254 let trimmed = self.arguments.trim();
255 if trimmed.is_empty() {
256 return Ok(serde_json::Value::Object(Default::default()));
257 }
258 serde_json::from_str(trimmed)
259 }
260}
261
262fn default_tool_type() -> String {
263 "function".to_string()
264}