1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum Role {
12 System,
14 User,
16 Assistant,
18 Tool,
20}
21
22#[derive(Debug, Clone, PartialEq)]
28pub struct ChatMessage {
29 pub role: Role,
31
32 pub content: Option<String>,
35
36 pub content_parts: Option<Vec<serde_json::Value>>,
41
42 pub tool_calls: Option<Vec<ToolCall>>,
44
45 pub tool_call_id: Option<String>,
47
48 pub name: Option<String>,
50
51 pub metadata: std::collections::BTreeMap<String, String>,
57}
58
59impl serde::Serialize for ChatMessage {
60 fn serialize<S: serde::Serializer>(&self, ser: S) -> std::result::Result<S::Ok, S::Error> {
61 use serde::ser::SerializeMap;
62 let mut m = ser.serialize_map(None)?;
63 m.serialize_entry("role", &self.role)?;
64 if let Some(parts) = &self.content_parts {
66 m.serialize_entry("content", parts)?;
67 } else if let Some(c) = &self.content {
68 m.serialize_entry("content", c)?;
69 }
70 if let Some(tc) = &self.tool_calls {
71 m.serialize_entry("tool_calls", tc)?;
72 }
73 if let Some(id) = &self.tool_call_id {
74 m.serialize_entry("tool_call_id", id)?;
75 }
76 if let Some(n) = &self.name {
77 m.serialize_entry("name", n)?;
78 }
79 m.end()
80 }
81}
82
83impl<'de> serde::Deserialize<'de> for ChatMessage {
84 fn deserialize<D: serde::Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
85 #[derive(Deserialize)]
86 struct Raw {
87 role: Role,
88 #[serde(default)]
89 content: Option<serde_json::Value>,
90 #[serde(default)]
91 tool_calls: Option<Vec<ToolCall>>,
92 #[serde(default)]
93 tool_call_id: Option<String>,
94 #[serde(default)]
95 name: Option<String>,
96 }
97 let raw = Raw::deserialize(de)?;
98 let (content, content_parts) = match raw.content {
100 Some(serde_json::Value::String(s)) => (Some(s), None),
101 Some(serde_json::Value::Array(a)) => (None, Some(a)),
102 Some(serde_json::Value::Null) | None => (None, None),
103 Some(other) => (Some(other.to_string()), None),
104 };
105 Ok(ChatMessage {
106 role: raw.role,
107 content,
108 content_parts,
109 tool_calls: raw.tool_calls,
110 tool_call_id: raw.tool_call_id,
111 name: raw.name,
112 metadata: Default::default(),
113 })
114 }
115}
116
117impl ChatMessage {
118 pub fn system(content: impl Into<String>) -> Self {
120 Self::text(Role::System, content)
121 }
122
123 pub fn user_with_images(text: impl Into<String>, image_urls: &[String]) -> Self {
127 let mut parts = vec![serde_json::json!({"type": "text", "text": text.into()})];
128 for url in image_urls {
129 parts.push(serde_json::json!({"type": "image_url", "image_url": {"url": url}}));
130 }
131 ChatMessage {
132 role: Role::User,
133 content: None,
134 content_parts: Some(parts),
135 tool_calls: None,
136 tool_call_id: None,
137 name: None,
138 metadata: Default::default(),
139 }
140 }
141
142 pub fn user(content: impl Into<String>) -> Self {
144 Self::text(Role::User, content)
145 }
146
147 pub fn assistant(content: impl Into<String>) -> Self {
149 Self::text(Role::Assistant, content)
150 }
151
152 pub fn tool_result(
154 tool_call_id: impl Into<String>,
155 name: impl Into<String>,
156 content: impl Into<String>,
157 ) -> Self {
158 ChatMessage {
159 role: Role::Tool,
160 content: Some(content.into()),
161 content_parts: None,
162 tool_calls: None,
163 tool_call_id: Some(tool_call_id.into()),
164 name: Some(name.into()),
165 metadata: Default::default(),
166 }
167 }
168
169 pub fn tool_result_with_image(
176 tool_call_id: impl Into<String>,
177 name: impl Into<String>,
178 notice: impl Into<String>,
179 data_url: impl Into<String>,
180 ) -> Self {
181 ChatMessage {
182 role: Role::Tool,
183 content: None,
184 content_parts: Some(vec![
185 serde_json::json!({"type": "text", "text": notice.into()}),
186 serde_json::json!({"type": "image_url", "image_url": {"url": data_url.into()}}),
187 ]),
188 tool_calls: None,
189 tool_call_id: Some(tool_call_id.into()),
190 name: Some(name.into()),
191 metadata: Default::default(),
192 }
193 }
194
195 fn text(role: Role, content: impl Into<String>) -> Self {
196 ChatMessage {
197 role,
198 content: Some(content.into()),
199 content_parts: None,
200 tool_calls: None,
201 tool_call_id: None,
202 name: None,
203 metadata: Default::default(),
204 }
205 }
206
207 pub fn tool_calls(&self) -> &[ToolCall] {
209 self.tool_calls.as_deref().unwrap_or(&[])
210 }
211
212 pub fn with_meta(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
214 self.metadata.insert(key.into(), value.into());
215 self
216 }
217
218 pub fn with_metas(mut self, pairs: &[(String, String)]) -> Self {
220 for (k, v) in pairs {
221 self.metadata.insert(k.clone(), v.clone());
222 }
223 self
224 }
225}
226
227pub const TOOL_ERROR_METADATA_KEY: &str = "sc.tool_error";
229
230pub const TOOL_OUTCOME_UNKNOWN_METADATA_KEY: &str = "sc.tool_outcome_unknown";
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239pub enum ToolOutcome {
240 KnownSuccess,
243 KnownError,
245 Unknown,
247}
248
249pub fn mark_tool_error(message: &mut ChatMessage) {
251 message.metadata.remove(TOOL_OUTCOME_UNKNOWN_METADATA_KEY);
252 message
253 .metadata
254 .insert(TOOL_ERROR_METADATA_KEY.to_string(), "true".to_string());
255}
256
257pub fn mark_tool_outcome_unknown(message: &mut ChatMessage) {
259 message.metadata.remove(TOOL_ERROR_METADATA_KEY);
260 message.metadata.insert(
261 TOOL_OUTCOME_UNKNOWN_METADATA_KEY.to_string(),
262 "true".to_string(),
263 );
264}
265
266pub fn is_tool_error(message: &ChatMessage) -> bool {
268 message
269 .metadata
270 .get(TOOL_ERROR_METADATA_KEY)
271 .map(String::as_str)
272 == Some("true")
273}
274
275pub fn tool_outcome(message: &ChatMessage) -> ToolOutcome {
277 if is_tool_error(message) {
278 ToolOutcome::KnownError
279 } else if message
280 .metadata
281 .get(TOOL_OUTCOME_UNKNOWN_METADATA_KEY)
282 .map(String::as_str)
283 == Some("true")
284 {
285 ToolOutcome::Unknown
286 } else {
287 ToolOutcome::KnownSuccess
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
293pub struct ToolCall {
294 pub id: String,
296
297 #[serde(rename = "type", default = "default_tool_type")]
299 pub kind: String,
300
301 pub function: FunctionCall,
303}
304
305#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307pub struct FunctionCall {
308 pub name: String,
310
311 pub arguments: String,
313}
314
315impl FunctionCall {
316 pub fn parsed_arguments(&self) -> serde_json::Result<serde_json::Value> {
320 let trimmed = self.arguments.trim();
321 if trimmed.is_empty() {
322 return Ok(serde_json::Value::Object(Default::default()));
323 }
324 serde_json::from_str(trimmed)
325 }
326}
327
328fn default_tool_type() -> String {
329 "function".to_string()
330}