gproxy-transform 3.0.0-alpha.0

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

use crate::TransformError;
use crate::common::usage;

pub(crate) fn transform(body: bytes::Bytes) -> Result<bytes::Bytes, TransformError> {
    let input: openai::ChatCompletionResponse = serde_json::from_slice(&body)?;
    let output = transform_typed(input)?;
    Ok(bytes::Bytes::from(serde_json::to_vec(&output)?))
}

pub(crate) fn transform_typed(
    input: openai::ChatCompletionResponse,
) -> Result<openai::ResponseObject, TransformError> {
    let id = input.id.clone();
    let mut output = Vec::new();
    let mut output_text = None;
    let mut status = openai::ResponseStatus::Completed;
    let mut incomplete_details = None;
    let choice = input.choices.into_iter().next();
    if let Some(reasoning) = choice
        .as_ref()
        .and_then(|choice| choice.message.reasoning_content.clone())
    {
        output.push(openai::ResponseItem::Typed(Box::new(
            openai::TypedResponseItem::Reasoning {
                id: None,
                summary: Vec::new(),
                content: Some(vec![crate::wire!(openai::ResponseReasoningTextPart {
                    type_: openai::ResponseReasoningTextType::ReasoningText,
                    text: reasoning,
                    rest: Default::default(),
                })]),
                encrypted_content: None,
                status: Some(openai::ResponseItemLifecycleStatus::Completed),
                rest: Default::default(),
            },
        )));
    }
    if let Some(text) = choice
        .as_ref()
        .and_then(|choice| choice.message.content.clone())
    {
        output_text = (!text.is_empty()).then(|| text.clone());
        output.push(openai::ResponseItem::Message(
            openai::ResponseMessageItem::Output(crate::wire!(openai::ResponseOutputMessageItem {
                type_: openai::ResponseMessageItemType::Message,
                id: format!("msg_{}", choice.as_ref().expect("choice exists").index),
                role: openai::ResponseOutputMessageRole::Assistant,
                content: vec![openai::ResponseMessageOutputContentPart::OutputText(
                    crate::wire!(openai::ResponseOutputText {
                        type_: openai::ResponseOutputTextType::OutputText,
                        annotations: Vec::new(),
                        logprobs: None,
                        text,
                        rest: Default::default(),
                    }),
                )],
                status: openai::ResponseItemLifecycleStatus::Completed,
                phase: None,
                rest: Default::default(),
            })),
        ));
    }
    for call in choice
        .as_ref()
        .and_then(|choice| choice.message.tool_calls.clone())
        .into_iter()
        .flatten()
    {
        output.push(match call {
            openai::ChatToolCall::Function(call) => {
                let call_id = response_call_id(&call.id);
                openai::ResponseItem::Typed(Box::new(openai::TypedResponseItem::FunctionCall {
                    arguments: call.function.arguments,
                    call_id,
                    name: call.function.name,
                    id: Some(response_item_id(&call.id)),
                    caller: None,
                    namespace: None,
                    async_: None,
                    status: Some(openai::ResponseItemLifecycleStatus::Completed),
                    rest: Default::default(),
                }))
            }
            openai::ChatToolCall::Custom(call) => {
                let call_id = response_call_id(&call.id);
                openai::ResponseItem::Typed(Box::new(openai::TypedResponseItem::CustomToolCall {
                    call_id,
                    input: call.custom.input,
                    name: call.custom.name,
                    id: None,
                    caller: None,
                    namespace: None,
                    async_: None,
                    rest: Default::default(),
                }))
            }
            openai::ChatToolCall::Unknown(_) => continue,
            #[cfg(not(feature = "exhaustive"))]
            _ => {
                return Err(crate::TransformError::unsupported(
                    "protocol enum",
                    "unrecognized external variant",
                ));
            }
        });
    }
    if let Some(reason) = choice.as_ref().map(|choice| &choice.finish_reason)
        && matches!(
            reason,
            openai::ChatFinishReason::Length | openai::ChatFinishReason::ContentFilter
        )
    {
        status = openai::ResponseStatus::Incomplete;
        incomplete_details = Some(crate::wire!(openai::IncompleteDetails {
            reason: Some(
                if matches!(reason, openai::ChatFinishReason::ContentFilter) {
                    openai::IncompleteReason::ContentFilter
                } else {
                    openai::IncompleteReason::MaxOutputTokens
                },
            ),
            rest: Default::default(),
        }));
    }
    let response = crate::wire!(openai::ResponseObject {
        id,
        created_at: input.created,
        background: None,
        completed_at: input.created,
        conversation: None,
        error: None,
        incomplete_details,
        instructions: None,
        max_output_tokens: None,
        max_tool_calls: None,
        metadata: None,
        model: Some(input.model),
        moderation: None,
        multi_agent: None,
        object: openai::ResponseObjectType::Response,
        output_text,
        output,
        parallel_tool_calls: None,
        prompt: None,
        prompt_cache_key: None,
        prompt_cache_options: None,
        prompt_cache_retention: None,
        previous_response_id: None,
        reasoning: None,
        safety_identifier: None,
        service_tier: input.service_tier,
        status: Some(status),
        store: None,
        temperature: None,
        text: None,
        tool_choice: None,
        tools: None,
        top_logprobs: None,
        top_p: None,
        truncation: None,
        usage: input.usage.map(usage::chat_to_responses),
        user: None,
        rest: Default::default(),
    });
    Ok(response)
}

fn response_call_id(original: &str) -> String {
    prefixed_id(original, "call_")
}

fn response_item_id(original: &str) -> String {
    prefixed_id(original, "fc_")
}

fn prefixed_id(original: &str, prefix: &str) -> String {
    if original.starts_with(prefix.trim_end_matches('_')) {
        return original.to_owned();
    }
    let mut hash = 0xcbf2_9ce4_8422_2325_u64;
    for byte in original.bytes() {
        hash ^= u64::from(byte);
        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
    }
    format!("{prefix}{hash:016x}")
}