gproxy-transform 3.0.0-alpha.0

GPROXY v3 pairwise protocol transforms
Documentation
use gproxy_protocol::openai;

use crate::TransformError;

mod parts;
mod tool_output;

use parts::response_part_to_chat;

pub(super) fn output_to_chat(
    output: openai::ResponseOutput,
) -> Result<openai::ChatTextContent, TransformError> {
    tool_output::output_to_chat(output)
}

pub(super) fn user_text(text: String) -> openai::ChatCompletionMessageParam {
    openai::ChatCompletionMessageParam::User(crate::wire!(openai::ChatUserMessageParam {
        role: openai::ChatUserRole::User,
        content: openai::ChatContent::Text(text),
        name: None,
        rest: Default::default(),
    }))
}

pub(super) fn user_content(
    content: openai::ResponseEasyInputContent,
) -> Result<openai::ChatCompletionMessageParam, TransformError> {
    Ok(openai::ChatCompletionMessageParam::User(crate::wire!(
        openai::ChatUserMessageParam {
            role: openai::ChatUserRole::User,
            content: easy_user(content)?,
            name: None,
            rest: Default::default(),
        }
    )))
}

pub(super) fn text_message(
    content: openai::ResponseEasyInputContent,
    role: openai::ResponseEasyInputMessageRole,
) -> Result<openai::ChatCompletionMessageParam, TransformError> {
    let content = easy_text(content)?;
    Ok(match role {
        openai::ResponseEasyInputMessageRole::System => {
            openai::ChatCompletionMessageParam::System(openai::ChatSystemMessageParam {
                role: openai::ChatSystemRole::System,
                content,
                name: None,
                rest: Default::default(),
            })
        }
        openai::ResponseEasyInputMessageRole::Developer
        | openai::ResponseEasyInputMessageRole::User
        | openai::ResponseEasyInputMessageRole::Assistant => {
            openai::ChatCompletionMessageParam::Developer(openai::ChatDeveloperMessageParam {
                role: openai::ChatDeveloperRole::Developer,
                content,
                name: None,
                rest: Default::default(),
            })
        }
        #[cfg(not(feature = "exhaustive"))]
        _ => {
            return Err(crate::TransformError::unsupported(
                "protocol enum",
                "unrecognized external variant",
            ));
        }
    })
}

pub(super) fn easy_text(
    content: openai::ResponseEasyInputContent,
) -> Result<openai::ChatTextContent, TransformError> {
    match content {
        openai::ResponseEasyInputContent::Text(text) => Ok(openai::ChatTextContent::Text(text)),
        openai::ResponseEasyInputContent::Parts(parts) => Ok(openai::ChatTextContent::Parts(
            parts
                .into_iter()
                .map(|part| match part {
                    openai::ResponseInputContentPart::InputText(part) => Ok(
                        openai::ChatTextContentPart::Text(crate::wire!(openai::ChatTextPart {
                            type_: openai::ChatTextPartType::Text,
                            text: part.text,
                            prompt_cache_breakpoint: part.prompt_cache_breakpoint,
                            rest: Default::default(),
                        })),
                    ),
                    unsupported @ (openai::ResponseInputContentPart::InputImage(_)
                    | openai::ResponseInputContentPart::InputFile(_)
                    | openai::ResponseInputContentPart::InputAudio(_)) => {
                        Err(TransformError::unsupported(
                            "Responses text content",
                            serde_json::to_string(&unsupported)?,
                        ))
                    }
                    #[cfg(not(feature = "exhaustive"))]
                    _ => {
                        return Err(crate::TransformError::unsupported(
                            "protocol enum",
                            "unrecognized external variant",
                        ));
                    }
                })
                .collect::<Result<_, _>>()?,
        )),
        openai::ResponseEasyInputContent::Unknown(raw) => Err(TransformError::unsupported(
            "OpenAI Responses text content",
            raw.to_string(),
        )),
        openai::ResponseEasyInputContent::OutputParts(parts) => {
            Ok(openai::ChatTextContent::Parts(output_text_parts(parts)))
        }
        #[cfg(not(feature = "exhaustive"))]
        _ => {
            return Err(crate::TransformError::unsupported(
                "protocol enum",
                "unrecognized external variant",
            ));
        }
    }
}

pub(super) fn easy_user(
    content: openai::ResponseEasyInputContent,
) -> Result<openai::ChatContent, TransformError> {
    match content {
        openai::ResponseEasyInputContent::Text(text) => Ok(openai::ChatContent::Text(text)),
        openai::ResponseEasyInputContent::Parts(parts) => Ok(openai::ChatContent::Parts(
            parts
                .into_iter()
                .map(response_part_to_chat)
                .collect::<Result<_, _>>()?,
        )),
        openai::ResponseEasyInputContent::Unknown(raw) => Err(TransformError::unsupported(
            "OpenAI Responses user content",
            raw.to_string(),
        )),
        openai::ResponseEasyInputContent::OutputParts(parts) => Ok(openai::ChatContent::Parts(
            output_text_parts(parts)
                .into_iter()
                .filter_map(|part| match part {
                    openai::ChatTextContentPart::Text(part) => {
                        Some(openai::ChatContentPart::Text(part))
                    }
                    openai::ChatTextContentPart::Unknown(_) => None,
                    #[cfg(not(feature = "exhaustive"))]
                    _ => None,
                })
                .collect(),
        )),
        #[cfg(not(feature = "exhaustive"))]
        _ => {
            return Err(crate::TransformError::unsupported(
                "protocol enum",
                "unrecognized external variant",
            ));
        }
    }
}

fn output_text_parts(
    parts: Vec<openai::ResponseMessageOutputContentPart>,
) -> Vec<openai::ChatTextContentPart> {
    parts
        .into_iter()
        .filter_map(|part| match part {
            openai::ResponseMessageOutputContentPart::OutputText(part) => Some(
                openai::ChatTextContentPart::Text(crate::wire!(openai::ChatTextPart {
                    type_: openai::ChatTextPartType::Text,
                    text: part.text,
                    prompt_cache_breakpoint: None,
                    rest: Default::default(),
                })),
            ),
            openai::ResponseMessageOutputContentPart::Refusal(part) => Some(
                openai::ChatTextContentPart::Text(crate::wire!(openai::ChatTextPart {
                    type_: openai::ChatTextPartType::Text,
                    text: part.refusal,
                    prompt_cache_breakpoint: None,
                    rest: Default::default(),
                })),
            ),
            openai::ResponseMessageOutputContentPart::Unknown(_) => None,
            #[cfg(not(feature = "exhaustive"))]
            _ => None,
        })
        .collect()
}

pub(super) fn easy_assistant(
    content: openai::ResponseEasyInputContent,
) -> Result<openai::ChatAssistantContent, TransformError> {
    match content {
        openai::ResponseEasyInputContent::Text(text) => {
            Ok(openai::ChatAssistantContent::Text(text))
        }
        openai::ResponseEasyInputContent::OutputParts(parts) => Ok(output_content(parts)),
        openai::ResponseEasyInputContent::Unknown(raw) => Err(TransformError::unsupported(
            "OpenAI Responses assistant content",
            raw.to_string(),
        )),
        unsupported @ openai::ResponseEasyInputContent::Parts(_) => {
            Err(TransformError::unsupported(
                "Responses assistant content",
                serde_json::to_string(&unsupported)?,
            ))
        }
        #[cfg(not(feature = "exhaustive"))]
        _ => {
            return Err(crate::TransformError::unsupported(
                "protocol enum",
                "unrecognized external variant",
            ));
        }
    }
}

pub(super) fn output_content(
    parts: Vec<openai::ResponseMessageOutputContentPart>,
) -> openai::ChatAssistantContent {
    openai::ChatAssistantContent::Parts(
        parts
            .into_iter()
            .filter_map(|part| match part {
                openai::ResponseMessageOutputContentPart::OutputText(part) => Some(
                    openai::ChatAssistantContentPart::Text(crate::wire!(openai::ChatTextPart {
                        type_: openai::ChatTextPartType::Text,
                        text: part.text,
                        prompt_cache_breakpoint: None,
                        rest: Default::default(),
                    })),
                ),
                openai::ResponseMessageOutputContentPart::Refusal(part) => {
                    Some(openai::ChatAssistantContentPart::Refusal(crate::wire!(
                        openai::ChatRefusalPart {
                            type_: openai::ChatRefusalPartType::Refusal,
                            refusal: part.refusal,
                            prompt_cache_breakpoint: None,
                            rest: Default::default(),
                        }
                    )))
                }
                openai::ResponseMessageOutputContentPart::Unknown(_) => None,
                #[cfg(not(feature = "exhaustive"))]
                _ => None,
            })
            .collect(),
    )
}