gproxy-transform 2.2.6

Pairwise request/response/stream transforms between the OpenAI, Anthropic Claude, and Google Gemini APIs
Documentation
use crate::protocol::{gemini, openai};

use super::mime::{infer_mime_type_from_uri, is_image_mime, parse_data_url};

pub(in crate::transform::images) fn prompt_content(
    prompt: String,
    images: Vec<openai::ImageReference>,
) -> gemini::Content {
    let mut parts = vec![text_part(prompt)];
    parts.extend(images.into_iter().map(image_reference_part));

    gemini::Content {
        parts,
        role: Some(gemini::ContentRole::Known(gemini::ContentRoleKnown::User)),
        extra: Default::default(),
    }
}

pub(super) fn text_part(text: String) -> gemini::Part {
    gemini::Part {
        thought: None,
        thought_signature: None,
        part_metadata: None,
        media_resolution: None,
        data: Some(gemini::PartData::Text { text }),
        metadata: None,
        extra: Default::default(),
    }
}

pub(super) fn inline_image_part(data: String, mime_type: String) -> gemini::Part {
    gemini::Part {
        thought: None,
        thought_signature: None,
        part_metadata: None,
        media_resolution: None,
        data: Some(gemini::PartData::InlineData {
            inline_data: gemini::Blob {
                mime_type,
                data,
                extra: Default::default(),
            },
        }),
        metadata: None,
        extra: Default::default(),
    }
}

pub(super) fn file_image_part(file_uri: String, mime_type: Option<String>) -> gemini::Part {
    gemini::Part {
        thought: None,
        thought_signature: None,
        part_metadata: None,
        media_resolution: None,
        data: Some(gemini::PartData::FileData {
            file_data: gemini::FileData {
                mime_type,
                file_uri,
                extra: Default::default(),
            },
        }),
        metadata: None,
        extra: Default::default(),
    }
}

fn image_reference_part(reference: openai::ImageReference) -> gemini::Part {
    if let Some(url) = reference.image_url {
        if let Some((mime_type, data)) = parse_data_url(&url) {
            return inline_image_part(data.to_owned(), mime_type.to_owned());
        }
        let mime_type = infer_mime_type_from_uri(Some(&url));
        return file_image_part(url, mime_type);
    }

    file_image_part(reference.file_id.unwrap_or_default(), None)
}

pub(in crate::transform::images) fn gemini_request_prompt(
    request: &gemini::GenerateContentRequest,
) -> String {
    let mut values = Vec::new();

    if let Some(system) = request.system_instruction.as_ref() {
        push_text_content(&mut values, system);
    }

    for content in &request.contents {
        push_text_content(&mut values, content);
    }

    values.join("\n")
}

pub(super) fn push_text_content(values: &mut Vec<String>, content: &gemini::Content) {
    for part in &content.parts {
        if let Some(gemini::PartData::Text { text }) = part.data.as_ref()
            && !text.is_empty()
        {
            values.push(text.clone());
        }
    }
}

pub(in crate::transform::images) fn gemini_request_image_references(
    request: &gemini::GenerateContentRequest,
) -> Vec<openai::ImageReference> {
    request
        .contents
        .iter()
        .flat_map(|content| content.parts.iter())
        .filter_map(part_to_openai_image_reference)
        .collect()
}

fn part_to_openai_image_reference(part: &gemini::Part) -> Option<openai::ImageReference> {
    match part.data.as_ref()? {
        gemini::PartData::InlineData { inline_data } if is_image_mime(&inline_data.mime_type) => {
            Some(openai_image_url_reference(format!(
                "data:{};base64,{}",
                inline_data.mime_type, inline_data.data
            )))
        }
        gemini::PartData::FileData { file_data }
            if file_data
                .mime_type
                .as_deref()
                .map(is_image_mime)
                .unwrap_or(true) =>
        {
            Some(openai_image_url_reference(file_data.file_uri.clone()))
        }
        _ => None,
    }
}

fn openai_image_url_reference(image_url: String) -> openai::ImageReference {
    openai::ImageReference {
        file_id: None,
        image_url: Some(image_url),
        extra: Default::default(),
    }
}