gproxy-protocol 4.1.11

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use crate::{
    transform::TransformError,
    wire::openai::{chat, responses},
};

pub(super) fn user_message(
    content: chat::content::UserContent,
) -> Result<responses::input::InputItem, TransformError> {
    let content = match content {
        chat::content::UserContentParts::Text(text) => responses::input::MessageContent::Text(text),
        chat::content::UserContentParts::Parts(parts) => responses::input::MessageContent::Parts(
            parts
                .into_iter()
                .map(|part| match part {
                    chat::content::UserContentPart::Text(part) => Ok(
                        responses::input::InputContent::Text(responses::input::ResponseInputText {
                            type_: responses::input::ResponseInputTextType::ResponseInputText,
                            text: part.text,
                            prompt_cache_breakpoint: part
                                .prompt_cache_breakpoint
                                .map(to_responses_cache),
                            rest: Default::default(),
                        }),
                    ),
                    chat::content::UserContentPart::Image(part) => {
                        Ok(responses::input::InputContent::Image(
                            responses::input::ResponseInputImage {
                                type_: responses::input::ResponseInputImageType::ResponseInputImage,
                                detail: match part
                                    .image_url
                                    .detail
                                    .unwrap_or(chat::content::ImageDetail::Auto)
                                {
                                    chat::content::ImageDetail::Auto => {
                                        responses::input::ImageDetail::Auto
                                    }
                                    chat::content::ImageDetail::Low => {
                                        responses::input::ImageDetail::Low
                                    }
                                    chat::content::ImageDetail::High => {
                                        responses::input::ImageDetail::High
                                    }
                                },
                                file_id: None,
                                image_url: Some(Some(part.image_url.url)),
                                prompt_cache_breakpoint: part
                                    .prompt_cache_breakpoint
                                    .map(to_responses_cache),
                                rest: Default::default(),
                            },
                        ))
                    }
                    chat::content::UserContentPart::File(part) => Ok(
                        responses::input::InputContent::File(responses::input::ResponseInputFile {
                            type_: responses::input::ResponseInputFileType::ResponseInputFile,
                            detail: None,
                            file_data: part.file.file_data,
                            file_id: part.file.file_id.map(Some),
                            file_url: None,
                            filename: part.file.filename,
                            prompt_cache_breakpoint: part
                                .prompt_cache_breakpoint
                                .map(to_responses_cache),
                            rest: Default::default(),
                        }),
                    ),
                    chat::content::UserContentPart::InputAudio(_) => {
                        Err(TransformError::unsupported(
                            "messages.content",
                            "Chat audio has no Responses input audio equivalent",
                        ))
                    }
                })
                .filter_map(|value| crate::transform::optional(value).transpose())
                .collect::<Result<Vec<_>, _>>()?,
        ),
    };
    Ok(responses::input::InputItem::Easy(
        responses::input::EasyInputMessage {
            agent: None,
            content,
            role: responses::input::MessageRole::User,
            phase: None,
            type_: None,
            rest: Default::default(),
        },
    ))
}

pub(super) fn to_chat(
    content: responses::input::MessageContent,
    report: &mut crate::transform::Report,
) -> Result<chat::UserContent, TransformError> {
    use responses::input as r;
    Ok(match content {
        r::MessageContent::Text(text) => chat::UserContent::Text(text),
        r::MessageContent::Parts(parts) => chat::UserContent::Parts(
            parts
                .into_iter()
                .map(|part| {
                    Ok(match part {
                        r::InputContent::Text(part) => {
                            let mut target =
                                chat::TextPart::builder(chat::TextPartType::Text, part.text)
                                    .build();
                            target.prompt_cache_breakpoint =
                                part.prompt_cache_breakpoint.map(to_chat_cache);
                            chat::UserContentPart::Text(target)
                        }
                        r::InputContent::Image(part) => {
                            if part.file_id.flatten().is_some() {
                                return Err(TransformError::missing_metadata(
                                    "input_image.file_id resource URL",
                                ));
                            }
                            let url = part.image_url.flatten().ok_or_else(|| {
                                TransformError::missing_metadata("input_image.image_url")
                            })?;
                            let mut image = chat::ImageUrl::builder(url).build();
                            image.detail = Some(match part.detail {
                                r::ImageDetail::Auto => chat::ImageDetail::Auto,
                                r::ImageDetail::Low => chat::ImageDetail::Low,
                                r::ImageDetail::High => chat::ImageDetail::High,
                                r::ImageDetail::Original => {
                                    return Err(TransformError::unsupported(
                                        "input_image.detail",
                                        "Chat has no original-resolution mode",
                                    ));
                                }
                            });
                            let mut target =
                                chat::ImagePart::builder(chat::ImagePartType::ImageUrl, image)
                                    .build();
                            target.prompt_cache_breakpoint =
                                part.prompt_cache_breakpoint.map(to_chat_cache);
                            chat::UserContentPart::Image(target)
                        }
                        r::InputContent::File(part) => {
                            if part.file_url.is_some() {
                                return Err(TransformError::missing_metadata(
                                    "input_file.file_url resource data",
                                ));
                            }
                            if part.detail.is_some() {
                                report.omitted(
                                    "input_file.detail",
                                    "Chat file parts have no detail selector",
                                );
                            }
                            let mut file = chat::FileRef::builder().build();
                            file.file_data = part.file_data;
                            file.file_id = part.file_id.flatten();
                            file.filename = part.filename;
                            let mut target =
                                chat::FilePart::builder(chat::FilePartType::File, file).build();
                            target.prompt_cache_breakpoint =
                                part.prompt_cache_breakpoint.map(to_chat_cache);
                            chat::UserContentPart::File(target)
                        }
                    })
                })
                .collect::<Result<Vec<_>, TransformError>>()?,
        ),
    })
}

pub(super) fn to_responses_cache(
    value: chat::PromptCacheBreakpoint,
) -> responses::input::PromptCacheBreakpoint {
    let mut target = responses::input::PromptCacheBreakpoint::builder(
        responses::input::PromptCacheMode::Explicit,
    )
    .build();
    target.ttl = value.ttl.map(|ttl| match ttl {
        chat::PromptCacheBreakpointTtl::ThirtyMinutes => {
            responses::input::PromptCacheBreakpointTtl::ThirtyMinutes
        }
        chat::PromptCacheBreakpointTtl::TwelveHours => {
            responses::input::PromptCacheBreakpointTtl::TwelveHours
        }
    });
    target
}

pub(super) fn to_chat_cache(
    value: responses::input::PromptCacheBreakpoint,
) -> chat::PromptCacheBreakpoint {
    let mut target = chat::PromptCacheBreakpoint::builder(chat::PromptCacheMode::Explicit).build();
    target.ttl = value.ttl.map(|ttl| match ttl {
        responses::input::PromptCacheBreakpointTtl::ThirtyMinutes => {
            chat::PromptCacheBreakpointTtl::ThirtyMinutes
        }
        responses::input::PromptCacheBreakpointTtl::TwelveHours => {
            chat::PromptCacheBreakpointTtl::TwelveHours
        }
    });
    target
}

pub(super) fn to_responses_text(part: chat::TextPart) -> responses::input::ResponseInputText {
    let mut target = responses::input::ResponseInputText::builder(
        responses::input::ResponseInputTextType::ResponseInputText,
        part.text,
    )
    .build();
    target.prompt_cache_breakpoint = part.prompt_cache_breakpoint.map(to_responses_cache);
    target
}

pub(super) fn to_chat_text(
    text: String,
    cache: Option<responses::input::PromptCacheBreakpoint>,
) -> chat::TextPart {
    let mut target = chat::TextPart::builder(chat::TextPartType::Text, text).build();
    target.prompt_cache_breakpoint = cache.map(to_chat_cache);
    target
}