use crate::ai::types::{Block, Message, Role};
use serde_json::{Value, json};
fn block_to_json(block: &Block) -> Value {
match block {
Block::Text(text) => json!({ "type": "text", "text": text }),
Block::Image { media_type, data_base64, name } => {
json!({ "type": "image", "media_type": media_type, "data": data_base64, "name": name })
}
Block::ToolUse { id, name, input } => json!({ "type": "tool_use", "id": id, "name": name, "input": input }),
Block::ToolResult { id, content, is_error } => {
json!({ "type": "tool_result", "id": id, "content": content, "is_error": is_error })
}
}
}
fn block_from_json(value: &Value) -> Option<Block> {
let text = |key: &str| value[key].as_str().map(str::to_owned);
match value["type"].as_str()? {
"text" => Some(Block::Text(text("text")?)),
"image" => Some(Block::Image {
media_type: text("media_type")?,
data_base64: text("data")?,
name: value["name"].as_str().map(str::to_owned),
}),
"tool_use" => Some(Block::ToolUse { id: text("id")?, name: text("name")?, input: value["input"].clone() }),
"tool_result" => Some(Block::ToolResult {
id: text("id")?,
content: text("content")?,
is_error: value["is_error"].as_bool().unwrap_or(false),
}),
_ => None,
}
}
pub fn messages_to_json(messages: &[Message]) -> Value {
messages
.iter()
.map(|message| {
let role = if message.role == Role::User { "user" } else { "assistant" };
json!({ "role": role, "blocks": message.blocks.iter().map(block_to_json).collect::<Vec<_>>() })
})
.collect()
}
pub fn messages_from_json(value: &Value) -> Vec<Message> {
value
.as_array()
.into_iter()
.flatten()
.filter_map(|message| {
let role = match message["role"].as_str()? {
"user" => Role::User,
"assistant" => Role::Assistant,
_ => return None,
};
let blocks: Vec<Block> = message["blocks"].as_array()?.iter().filter_map(block_from_json).collect();
(!blocks.is_empty()).then_some(Message { role, blocks })
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ida_y_vuelta_conserva_todo() {
let messages = vec![
Message {
role: Role::User,
blocks: vec![
Block::Text("mira".into()),
Block::Image {
media_type: "image/png".into(),
data_base64: "AAA".into(),
name: Some("foto.png".into()),
},
],
},
Message::assistant(vec![
Block::Text("miro".into()),
Block::ToolUse { id: "t".into(), name: "ls".into(), input: json!({"p": [1, 2]}) },
]),
Message {
role: Role::User,
blocks: vec![Block::ToolResult { id: "t".into(), content: "a".into(), is_error: true }],
},
];
assert_eq!(messages_from_json(&messages_to_json(&messages)), messages);
}
#[test]
fn lo_ilegible_se_omite() {
let value = json!([
{"role": "user", "blocks": [{"type": "text", "text": "ok"}, {"type": "raro"}]},
{"role": "otro", "blocks": []},
{"nada": 1},
]);
assert_eq!(messages_from_json(&value), [Message::user("ok")]);
}
}