gproxy-transform 3.0.0-alpha.0

GPROXY v3 pairwise protocol transforms
Documentation
mod events;

use gproxy_protocol::{claude, openai};

use crate::common::usage;

use super::claude_to_openai::State;

pub(in crate::common::stream) use events::ResponseDelta;

impl State {
    pub(super) fn response_object(&self, status: openai::ResponseStatus) -> openai::ResponseObject {
        crate::wire!(openai::ResponseObject {
            id: self.id.clone().expect("started message has an id"),
            created_at: None,
            background: None,
            completed_at: None,
            conversation: None,
            error: None,
            incomplete_details: match &self.stop_reason {
                claude::StopReason::Known(claude::StopReasonKnown::MaxTokens)
                | claude::StopReason::Known(claude::StopReasonKnown::ModelContextWindowExceeded) => {
                    Some(openai::IncompleteDetails {
                        reason: Some(openai::IncompleteReason::MaxOutputTokens),
                        rest: Default::default(),
                    })
                }
                claude::StopReason::Known(claude::StopReasonKnown::Refusal) => {
                    Some(openai::IncompleteDetails {
                        reason: Some(openai::IncompleteReason::ContentFilter),
                        rest: Default::default(),
                    })
                }
                _ => None,
            },
            instructions: None,
            max_output_tokens: None,
            max_tool_calls: None,
            metadata: None,
            model: self.model.clone(),
            moderation: None,
            multi_agent: None,
            object: openai::ResponseObjectType::Response,
            output: self.completed.clone(),
            output_text: None,
            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: None,
            status: Some(status),
            store: None,
            temperature: None,
            text: None,
            tool_choice: None,
            tools: None,
            top_logprobs: None,
            top_p: None,
            truncation: None,
            usage: self.usage.clone().and_then(usage::claude_to_responses),
            user: None,
            rest: Default::default(),
        })
    }

    pub(super) fn next_sequence(&mut self) -> u64 {
        let sequence = self.sequence;
        self.sequence += 1;
        sequence
    }
}

pub(super) fn reasoning_item(
    id: String,
    text: String,
    signature: Option<String>,
    status: openai::ResponseItemLifecycleStatus,
) -> openai::ResponseItem {
    openai::ResponseItem::Typed(Box::new(openai::TypedResponseItem::Reasoning {
        id: Some(id),
        summary: Vec::new(),
        content: Some(vec![crate::wire!(openai::ResponseReasoningTextPart {
            type_: openai::ResponseReasoningTextType::ReasoningText,
            text,
            rest: Default::default(),
        })]),
        encrypted_content: signature,
        status: Some(status),
        rest: Default::default(),
    }))
}

pub(super) fn function_item(
    id: String,
    name: String,
    arguments: String,
    status: openai::ResponseItemLifecycleStatus,
) -> openai::ResponseItem {
    openai::ResponseItem::Typed(Box::new(openai::TypedResponseItem::FunctionCall {
        arguments,
        call_id: id.clone(),
        name,
        id: Some(id),
        caller: None,
        namespace: None,
        async_: None,
        status: Some(status),
        rest: Default::default(),
    }))
}