gproxy-protocol 4.0.3

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use super::*;
use base64::Engine as _;

#[derive(Clone)]
pub(crate) enum Media {
    Image {
        url: String,
        detail: Option<source::ImageDetail>,
    },
    Audio {
        url: String,
    },
}

pub(super) fn media(input: &source::GuardianRequestBody) -> Result<Vec<Media>, TransformError> {
    let mut output = Vec::new();
    for entry in &input.input {
        let add_output = |body: &source::FunctionCallOutputBody, output: &mut Vec<Media>| {
            if let source::FunctionCallOutputBody::ContentItems(items) = body {
                for item in items {
                    match item {
                        source::FunctionCallOutputContentItem::InputImage(v) => {
                            output.push(Media::Image {
                                url: v.image_url.clone(),
                                detail: v.detail.clone(),
                            })
                        }
                        source::FunctionCallOutputContentItem::InputAudio(v) => {
                            output.push(Media::Audio {
                                url: v.audio_url.clone(),
                            })
                        }
                        _ => {}
                    }
                }
            }
        };
        match entry {
            source::ClientResponseItem::Message(v) => {
                for item in &v.content {
                    match item {
                        source::ContentItem::InputImage(x) => output.push(Media::Image {
                            url: x.image_url.clone(),
                            detail: x.detail.clone(),
                        }),
                        source::ContentItem::InputAudio(x) => output.push(Media::Audio {
                            url: x.audio_url.clone(),
                        }),
                        _ => {}
                    }
                }
            }
            source::ClientResponseItem::FunctionCallOutput(v) => add_output(&v.output, &mut output),
            source::ClientResponseItem::CustomToolCallOutput(v) => {
                add_output(&v.output, &mut output)
            }
            source::ClientResponseItem::ImageGenerationCall(v) => {
                let bytes = STANDARD.decode(&v.result).map_err(|e| {
                    TransformError::shape("guardian.image_generation.result", e.to_string())
                })?;
                let info = crate::transform::images::inspect_image(&bytes).map_err(|e| {
                    TransformError::shape("guardian.image_generation.result", e.detail())
                })?;
                output.push(Media::Image {
                    url: format!("data:{};base64,{}", info.mime(), v.result),
                    detail: None,
                });
            }
            _ => {}
        }
    }
    Ok(output)
}

pub(super) fn data_uri(value: &str) -> Result<(String, String), TransformError> {
    let (mime, data) = value
        .strip_prefix("data:")
        .and_then(|v| v.split_once(";base64,"))
        .ok_or_else(|| {
            TransformError::missing_metadata(
                "guardian media requires a resolved data URI with MIME",
            )
        })?;
    STANDARD
        .decode(data)
        .map_err(|e| TransformError::shape("guardian.media", e.to_string()))?;
    Ok((mime.into(), data.into()))
}

pub(super) fn detail(value: &Option<source::ImageDetail>) -> r::ImageDetail {
    match value {
        None | Some(source::ImageDetail::Auto) => r::ImageDetail::Auto,
        Some(source::ImageDetail::Low) => r::ImageDetail::Low,
        Some(source::ImageDetail::High) => r::ImageDetail::High,
        Some(source::ImageDetail::Original) => r::ImageDetail::Original,
    }
}

impl Media {
    pub(crate) fn url(&self) -> &str {
        match self {
            Self::Image { url, .. } | Self::Audio { url } => url,
        }
    }
    pub(crate) fn set_url(&mut self, value: String) {
        match self {
            Self::Image { url, .. } | Self::Audio { url } => *url = value,
        }
    }
    pub(crate) fn needs_resolution(&self, target: GuardianTarget) -> bool {
        !self.url().starts_with("data:")
            && (target == GuardianTarget::Gemini
                || matches!(
                    (target, self),
                    (GuardianTarget::OpenAiChat, Self::Audio { .. })
                ))
    }
}