use serde_json::{json, Value};
fn parse_data_uri(url: &str) -> Option<(&str, &str)> {
let stripped = url.strip_prefix("data:")?;
let (header, data) = stripped.split_once(",")?;
let media_type = header.strip_suffix(";base64")?;
Some((media_type, data))
}
pub fn to_anthropic(block: &Value) -> Option<Value> {
let block_type = block.get("type").and_then(|t| t.as_str())?;
match block_type {
"image_url" => {
let url = block.pointer("/image_url/url").and_then(|u| u.as_str())?;
Some(url_to_anthropic(url))
}
"input_image" => {
let url = block.get("image_url").and_then(|u| u.as_str())?;
Some(url_to_anthropic(url))
}
"image" => Some(block.clone()),
_ => None,
}
}
fn url_to_anthropic(url: &str) -> Value {
if let Some((media_type, data)) = parse_data_uri(url) {
json!({
"type": "image",
"source": {
"type": "base64",
"media_type": media_type,
"data": data
}
})
} else {
json!({
"type": "image",
"source": {
"type": "url",
"url": url
}
})
}
}
pub fn to_gemini(block: &Value) -> Option<Value> {
let block_type = block.get("type").and_then(|t| t.as_str())?;
match block_type {
"image_url" => {
let url = block.pointer("/image_url/url").and_then(|u| u.as_str())?;
Some(url_to_gemini(url))
}
"input_image" => {
let url = block.get("image_url").and_then(|u| u.as_str())?;
Some(url_to_gemini(url))
}
"image" => {
let source = block.get("source")?;
match source.get("type").and_then(|t| t.as_str())? {
"base64" => Some(json!({
"inline_data": {
"mime_type": source.get("media_type").and_then(|m| m.as_str()).unwrap_or("image/jpeg"),
"data": source.get("data").and_then(|d| d.as_str()).unwrap_or("")
}
})),
"url" => {
let url = source.get("url").and_then(|u| u.as_str()).unwrap_or("");
Some(json!({"text": format!("[Image: {}]", url)}))
}
_ => None,
}
}
_ => None,
}
}
fn url_to_gemini(url: &str) -> Value {
if let Some((media_type, data)) = parse_data_uri(url) {
json!({
"inline_data": {
"mime_type": media_type,
"data": data
}
})
} else {
json!({"text": format!("[Image: {}]", url)})
}
}
pub fn to_openai(block: &Value) -> Option<Value> {
let block_type = block.get("type").and_then(|t| t.as_str())?;
match block_type {
"input_image" => Some(block.clone()),
"image_url" => {
let url = block.pointer("/image_url/url").and_then(|u| u.as_str())?;
Some(json!({
"type": "input_image",
"image_url": url
}))
}
"image" => {
let source = block.get("source")?;
match source.get("type").and_then(|t| t.as_str())? {
"base64" => {
let media_type = source
.get("media_type")
.and_then(|m| m.as_str())
.unwrap_or("image/jpeg");
let data = source.get("data").and_then(|d| d.as_str()).unwrap_or("");
Some(json!({
"type": "input_image",
"image_url": format!("data:{};base64,{}", media_type, data)
}))
}
"url" => {
let url = source.get("url").and_then(|u| u.as_str())?;
Some(json!({
"type": "input_image",
"image_url": url
}))
}
_ => None,
}
}
_ => None,
}
}
pub fn translate_content_blocks(content: &Value, translator: fn(&Value) -> Option<Value>) -> Value {
match content {
Value::Array(blocks) => {
let translated: Vec<Value> = blocks
.iter()
.map(|block| {
let block_type = block.get("type").and_then(|t| t.as_str());
match block_type {
Some("text" | "input_text") => block.clone(),
Some("image_url" | "input_image" | "image") => {
translator(block).unwrap_or_else(|| block.clone())
}
_ => block.clone(), }
})
.collect();
Value::Array(translated)
}
_ => content.clone(), }
}
pub fn text_blocks_to_openai(content: &Value) -> Value {
match content {
Value::Array(blocks) => {
let translated: Vec<Value> = blocks
.iter()
.map(|block| {
if block.get("type").and_then(|t| t.as_str()) == Some("text") {
let mut out = block.clone();
out["type"] = Value::String("input_text".into());
out
} else {
block.clone()
}
})
.collect();
Value::Array(translated)
}
_ => content.clone(),
}
}