use super::common::*;
use super::*;
impl Message {
pub fn to_openai_message(&self, include_reasoning_content: bool) -> Value {
let mut payload = serde_json::Map::from_iter([
(
"role".to_string(),
Value::String(message_role_value(self.role).to_string()),
),
("content".to_string(), Value::String(self.content.clone())),
]);
insert_non_empty_optional_string(&mut payload, "name", &self.name);
insert_non_empty_optional_string(&mut payload, "tool_call_id", &self.tool_call_id);
if self.role == MessageRole::Assistant && !self.tool_calls.is_empty() {
payload.insert(
"tool_calls".to_string(),
Value::Array(self.tool_calls.iter().map(tool_call_to_openai).collect()),
);
if self.content.is_empty() {
payload.insert("content".to_string(), Value::Null);
}
}
if include_reasoning_content && self.role == MessageRole::Assistant {
insert_non_empty_optional_string(
&mut payload,
"reasoning_content",
&self.reasoning_content,
);
}
if self.role == MessageRole::User {
if let Some(image_url) = self.image_url.as_deref().filter(|value| !value.is_empty()) {
let mut blocks = Vec::new();
if !self.content.is_empty() {
blocks.push(serde_json::json!({
"type": "text",
"text": self.content,
}));
}
blocks.push(serde_json::json!({
"type": "image_url",
"image_url": {"url": image_url},
}));
payload.insert("content".to_string(), Value::Array(blocks));
}
}
Value::Object(payload)
}
pub fn to_dict(&self) -> Value {
let mut payload = serde_json::Map::from_iter([
(
"role".to_string(),
Value::String(message_role_value(self.role).to_string()),
),
("content".to_string(), Value::String(self.content.clone())),
]);
insert_optional_string(&mut payload, "name", &self.name);
insert_optional_string(&mut payload, "tool_call_id", &self.tool_call_id);
if !self.tool_calls.is_empty() {
payload.insert(
"tool_calls".to_string(),
Value::Array(self.tool_calls.iter().map(tool_call_to_openai).collect()),
);
}
insert_optional_string(&mut payload, "reasoning_content", &self.reasoning_content);
insert_optional_string(&mut payload, "image_url", &self.image_url);
if !self.metadata.is_empty() {
payload.insert("metadata".to_string(), metadata_to_value(&self.metadata));
}
if let Some(artifact_ref) = &self.artifact_ref {
payload.insert(
"artifact_ref".to_string(),
serde_json::to_value(artifact_ref).expect("ToolArtifactRef is serializable"),
);
}
Value::Object(payload)
}
pub fn from_dict(data: &Value) -> Result<Self, String> {
let object = expect_object(data, "Message")?;
const ALLOWED_FIELDS: &[&str] = &[
"role",
"content",
"name",
"tool_call_id",
"tool_calls",
"reasoning_content",
"image_url",
"metadata",
"artifact_ref",
];
let unknown = object
.keys()
.filter(|field| !ALLOWED_FIELDS.contains(&field.as_str()))
.cloned()
.collect::<Vec<_>>();
if !unknown.is_empty() {
return Err(format!("Message contains unknown fields: {unknown:?}"));
}
let role = parse_message_role(read_required_string(object, "role")?)?;
let content = read_required_string(object, "content")?.to_string();
let tool_calls = read_array(object, "tool_calls")
.unwrap_or(&[])
.iter()
.map(ToolCall::from_dict)
.collect::<Result<Vec<_>, _>>()?;
for field in ["name", "tool_call_id", "reasoning_content", "image_url"] {
if object
.get(field)
.is_some_and(|value| !value.is_null() && !value.is_string())
{
return Err(format!("Message field {field:?} must be a string or null"));
}
}
let artifact_ref = object
.get("artifact_ref")
.map(|value| {
if !value.is_object() {
return Err("Message field \"artifact_ref\" must be an object".to_string());
}
let artifact = serde_json::from_value::<ToolArtifactRef>(value.clone())
.map_err(|error| error.to_string())?;
artifact.validate()?;
Ok(artifact)
})
.transpose()?;
Ok(Self {
role,
content,
name: read_optional_string(object, "name"),
tool_call_id: read_optional_string(object, "tool_call_id"),
tool_calls,
reasoning_content: read_optional_string(object, "reasoning_content"),
image_url: read_optional_string(object, "image_url"),
metadata: read_metadata(object, "metadata")?,
artifact_ref,
})
}
}
fn tool_call_to_openai(tool_call: &ToolCall) -> Value {
let mut payload = serde_json::json!({
"id": tool_call.id,
"type": "function",
"function": {
"name": tool_call.name,
"arguments": Value::Object(tool_call.arguments.clone().into_iter().collect()).to_string(),
},
});
if let Some(extra_content) = &tool_call.extra_content {
payload["extra_content"] = extra_content.clone();
}
payload
}
impl ToolCall {
pub fn to_dict(&self) -> Value {
let mut payload = serde_json::Map::from_iter([
("id".to_string(), Value::String(self.id.clone())),
("name".to_string(), Value::String(self.name.clone())),
(
"arguments".to_string(),
Value::Object(self.arguments.clone().into_iter().collect()),
),
]);
if let Some(extra_content) = &self.extra_content {
payload.insert("extra_content".to_string(), extra_content.clone());
}
Value::Object(payload)
}
pub fn from_dict(data: &Value) -> Result<Self, String> {
let object = expect_object(data, "ToolCall")?;
if let Some(function) = object.get("function").and_then(Value::as_object) {
let id = read_required_string(object, "id")?.to_string();
let name = read_required_string(function, "name")?.to_string();
let raw_arguments = function
.get("arguments")
.cloned()
.unwrap_or_else(|| Value::String("{}".to_string()));
let mut tool_call = ToolCall::from_raw_arguments(id, name, raw_arguments);
tool_call.extra_content = object
.get("extra_content")
.filter(|value| value.is_object())
.cloned();
return Ok(tool_call);
}
Ok(Self {
id: read_required_string(object, "id")?.to_string(),
name: read_required_string(object, "name")?.to_string(),
arguments: read_metadata(object, "arguments")?,
extra_content: object
.get("extra_content")
.filter(|value| value.is_object())
.cloned(),
})
}
}