1use super::common::*;
2use super::*;
3
4impl Message {
5 pub fn to_openai_message(&self, include_reasoning_content: bool) -> Value {
6 let mut payload = serde_json::Map::from_iter([
7 (
8 "role".to_string(),
9 Value::String(message_role_value(self.role).to_string()),
10 ),
11 ("content".to_string(), Value::String(self.content.clone())),
12 ]);
13 insert_non_empty_optional_string(&mut payload, "name", &self.name);
14 insert_non_empty_optional_string(&mut payload, "tool_call_id", &self.tool_call_id);
15 if self.role == MessageRole::Assistant && !self.tool_calls.is_empty() {
16 payload.insert(
17 "tool_calls".to_string(),
18 Value::Array(self.tool_calls.iter().map(tool_call_to_openai).collect()),
19 );
20 if self.content.is_empty() {
21 payload.insert("content".to_string(), Value::Null);
22 }
23 }
24 if include_reasoning_content && self.role == MessageRole::Assistant {
25 insert_non_empty_optional_string(
26 &mut payload,
27 "reasoning_content",
28 &self.reasoning_content,
29 );
30 }
31 if self.role == MessageRole::User {
32 if let Some(image_url) = self.image_url.as_deref().filter(|value| !value.is_empty()) {
33 let mut blocks = Vec::new();
34 if !self.content.is_empty() {
35 blocks.push(serde_json::json!({
36 "type": "text",
37 "text": self.content,
38 }));
39 }
40 blocks.push(serde_json::json!({
41 "type": "image_url",
42 "image_url": {"url": image_url},
43 }));
44 payload.insert("content".to_string(), Value::Array(blocks));
45 }
46 }
47 Value::Object(payload)
48 }
49
50 pub fn to_dict(&self) -> Value {
51 let mut payload = serde_json::Map::from_iter([
52 (
53 "role".to_string(),
54 Value::String(message_role_value(self.role).to_string()),
55 ),
56 ("content".to_string(), Value::String(self.content.clone())),
57 ]);
58 insert_optional_string(&mut payload, "name", &self.name);
59 insert_optional_string(&mut payload, "tool_call_id", &self.tool_call_id);
60 if !self.tool_calls.is_empty() {
61 payload.insert(
62 "tool_calls".to_string(),
63 Value::Array(self.tool_calls.iter().map(tool_call_to_openai).collect()),
64 );
65 }
66 insert_optional_string(&mut payload, "reasoning_content", &self.reasoning_content);
67 insert_optional_string(&mut payload, "image_url", &self.image_url);
68 if !self.metadata.is_empty() {
69 payload.insert("metadata".to_string(), metadata_to_value(&self.metadata));
70 }
71 if let Some(artifact_ref) = &self.artifact_ref {
72 payload.insert(
73 "artifact_ref".to_string(),
74 serde_json::to_value(artifact_ref).expect("ToolArtifactRef is serializable"),
75 );
76 }
77 Value::Object(payload)
78 }
79
80 pub fn from_dict(data: &Value) -> Result<Self, String> {
81 let object = expect_object(data, "Message")?;
82 const ALLOWED_FIELDS: &[&str] = &[
83 "role",
84 "content",
85 "name",
86 "tool_call_id",
87 "tool_calls",
88 "reasoning_content",
89 "image_url",
90 "metadata",
91 "artifact_ref",
92 ];
93 let unknown = object
94 .keys()
95 .filter(|field| !ALLOWED_FIELDS.contains(&field.as_str()))
96 .cloned()
97 .collect::<Vec<_>>();
98 if !unknown.is_empty() {
99 return Err(format!("Message contains unknown fields: {unknown:?}"));
100 }
101 let role = parse_message_role(read_required_string(object, "role")?)?;
102 let content = read_required_string(object, "content")?.to_string();
103 let tool_calls = read_array(object, "tool_calls")
104 .unwrap_or(&[])
105 .iter()
106 .map(ToolCall::from_dict)
107 .collect::<Result<Vec<_>, _>>()?;
108 for field in ["name", "tool_call_id", "reasoning_content", "image_url"] {
109 if object
110 .get(field)
111 .is_some_and(|value| !value.is_null() && !value.is_string())
112 {
113 return Err(format!("Message field {field:?} must be a string or null"));
114 }
115 }
116 let artifact_ref = object
117 .get("artifact_ref")
118 .map(|value| {
119 if !value.is_object() {
120 return Err("Message field \"artifact_ref\" must be an object".to_string());
121 }
122 let artifact = serde_json::from_value::<ToolArtifactRef>(value.clone())
123 .map_err(|error| error.to_string())?;
124 artifact.validate()?;
125 Ok(artifact)
126 })
127 .transpose()?;
128 Ok(Self {
129 role,
130 content,
131 name: read_optional_string(object, "name"),
132 tool_call_id: read_optional_string(object, "tool_call_id"),
133 tool_calls,
134 reasoning_content: read_optional_string(object, "reasoning_content"),
135 image_url: read_optional_string(object, "image_url"),
136 metadata: read_metadata(object, "metadata")?,
137 artifact_ref,
138 })
139 }
140}
141
142fn tool_call_to_openai(tool_call: &ToolCall) -> Value {
143 let mut payload = serde_json::json!({
144 "id": tool_call.id,
145 "type": "function",
146 "function": {
147 "name": tool_call.name,
148 "arguments": Value::Object(tool_call.arguments.clone().into_iter().collect()).to_string(),
149 },
150 });
151 if let Some(extra_content) = &tool_call.extra_content {
152 payload["extra_content"] = extra_content.clone();
153 }
154 payload
155}
156
157impl ToolCall {
158 pub fn to_dict(&self) -> Value {
159 let mut payload = serde_json::Map::from_iter([
160 ("id".to_string(), Value::String(self.id.clone())),
161 ("name".to_string(), Value::String(self.name.clone())),
162 (
163 "arguments".to_string(),
164 Value::Object(self.arguments.clone().into_iter().collect()),
165 ),
166 ]);
167 if let Some(extra_content) = &self.extra_content {
168 payload.insert("extra_content".to_string(), extra_content.clone());
169 }
170 Value::Object(payload)
171 }
172
173 pub fn from_dict(data: &Value) -> Result<Self, String> {
174 let object = expect_object(data, "ToolCall")?;
175 if let Some(function) = object.get("function").and_then(Value::as_object) {
176 let id = read_required_string(object, "id")?.to_string();
177 let name = read_required_string(function, "name")?.to_string();
178 let raw_arguments = function
179 .get("arguments")
180 .cloned()
181 .unwrap_or_else(|| Value::String("{}".to_string()));
182 let mut tool_call = ToolCall::from_raw_arguments(id, name, raw_arguments);
183 tool_call.extra_content = object
184 .get("extra_content")
185 .filter(|value| value.is_object())
186 .cloned();
187 return Ok(tool_call);
188 }
189 Ok(Self {
190 id: read_required_string(object, "id")?.to_string(),
191 name: read_required_string(object, "name")?.to_string(),
192 arguments: read_metadata(object, "arguments")?,
193 extra_content: object
194 .get("extra_content")
195 .filter(|value| value.is_object())
196 .cloned(),
197 })
198 }
199}