gproxy-transform 3.0.0-alpha.0

GPROXY v3 pairwise protocol transforms
Documentation
use gproxy_protocol::{claude, openai};

use crate::TransformError;
use crate::common::native::items;

use super::State;
use super::item_id;

struct ToolStart {
    item_id: String,
    id: String,
    name: String,
    input: String,
    output_index: u32,
}

impl State {
    pub(super) fn response_output_item_added(
        &mut self,
        event: openai::ResponseOutputItemEvent,
    ) -> Result<Vec<claude::StreamEvent>, TransformError> {
        let mut output = self.ensure_start()?;
        let native_id = match event.item.as_ref() {
            openai::ResponseItem::Typed(item) => items::item_id(item),
            openai::ResponseItem::Message(_) | openai::ResponseItem::Unknown(_) => None,
            #[cfg(not(feature = "exhaustive"))]
            _ => {
                return Err(crate::TransformError::unsupported(
                    "protocol enum",
                    "unrecognized external variant",
                ));
            }
        };
        let item_id = item_id(&event.item).or(native_id);
        match *event.item {
            openai::ResponseItem::Typed(item) => match *item {
                openai::TypedResponseItem::FunctionCall {
                    call_id,
                    name,
                    arguments,
                    ..
                } => {
                    output.extend(self.response_tool(ToolStart {
                        item_id: item_id.ok_or_else(|| {
                            TransformError::shape("Responses stream", "function item id is missing")
                        })?,
                        id: call_id,
                        name,
                        input: arguments,
                        output_index: event.output_index,
                    })?);
                }
                openai::TypedResponseItem::CustomToolCall {
                    call_id,
                    name,
                    input,
                    ..
                } => {
                    output.extend(self.response_tool(ToolStart {
                        item_id: item_id.ok_or_else(|| {
                            TransformError::shape("Responses stream", "custom item id is missing")
                        })?,
                        id: call_id,
                        name,
                        input,
                        output_index: event.output_index,
                    })?);
                }
                openai::TypedResponseItem::Reasoning {
                    encrypted_content, ..
                } => {
                    let item_id = item_id.ok_or_else(|| {
                        TransformError::shape("Responses stream", "reasoning item id is missing")
                    })?;
                    let index = self.allocate();
                    output.extend(self.block_start(
                        index,
                        claude::ResponseContentBlock::Thinking(crate::wire!(
                            claude::ThinkingBlock {
                                signature: encrypted_content,
                                thinking: String::new(),
                                type_: claude::ThinkingBlockType::Thinking,
                                rest: Default::default(),
                            }
                        )),
                    )?);
                    self.response_indices.insert((item_id, None), index);
                }
                other @ (openai::TypedResponseItem::FileSearchCall { .. }
                | openai::TypedResponseItem::ComputerCall { .. }
                | openai::TypedResponseItem::ComputerCallOutput { .. }
                | openai::TypedResponseItem::WebSearchCall { .. }
                | openai::TypedResponseItem::FunctionCallOutput { .. }
                | openai::TypedResponseItem::ToolSearchCall { .. }
                | openai::TypedResponseItem::ToolSearchOutput { .. }
                | openai::TypedResponseItem::AdditionalTools { .. }
                | openai::TypedResponseItem::Compaction { .. }
                | openai::TypedResponseItem::ImageGenerationCall { .. }
                | openai::TypedResponseItem::CodeInterpreterCall { .. }
                | openai::TypedResponseItem::LocalShellCall { .. }
                | openai::TypedResponseItem::LocalShellCallOutput { .. }
                | openai::TypedResponseItem::ShellCall { .. }
                | openai::TypedResponseItem::ShellCallOutput { .. }
                | openai::TypedResponseItem::ApplyPatchCall { .. }
                | openai::TypedResponseItem::ApplyPatchCallOutput { .. }
                | openai::TypedResponseItem::McpListTools { .. }
                | openai::TypedResponseItem::McpApprovalRequest { .. }
                | openai::TypedResponseItem::McpApprovalResponse { .. }
                | openai::TypedResponseItem::McpCall { .. }
                | openai::TypedResponseItem::CustomToolCallOutput { .. }
                | openai::TypedResponseItem::Program { .. }
                | openai::TypedResponseItem::ProgramOutput { .. }
                | openai::TypedResponseItem::MultiAgentCall { .. }
                | openai::TypedResponseItem::MultiAgentCallOutput { .. }
                | openai::TypedResponseItem::AgentMessage { .. }
                | openai::TypedResponseItem::ConfigurationUpdate { .. }
                | openai::TypedResponseItem::CompactionTrigger { .. }
                | openai::TypedResponseItem::ItemReference { .. }) => {
                    if let Some(call) = items::openai_call(other.clone())? {
                        let index = self.allocate();
                        output.extend(self.block_start(index, items::response_block(call))?);
                        if let Some(item_id) = item_id {
                            self.response_indices.insert((item_id, None), index);
                        }
                        self.response_output_indices
                            .entry(event.output_index)
                            .or_default()
                            .push(index);
                        self.has_tool = true;
                    } else {
                        return Ok(output);
                    }
                }
                #[cfg(not(feature = "exhaustive"))]
                _ => {
                    return Err(crate::TransformError::unsupported(
                        "protocol enum",
                        "unrecognized external variant",
                    ));
                }
            },
            openai::ResponseItem::Message(openai::ResponseMessageItem::Output(message)) => {
                if !message.content.is_empty() {
                    return Ok(output);
                }
            }
            openai::ResponseItem::Message(openai::ResponseMessageItem::Input(_))
            | openai::ResponseItem::Message(openai::ResponseMessageItem::EasyInput(_))
            | openai::ResponseItem::Message(openai::ResponseMessageItem::Unknown(_))
            | openai::ResponseItem::Unknown(_) => return Ok(output),
            #[cfg(not(feature = "exhaustive"))]
            _ => {
                return Err(crate::TransformError::unsupported(
                    "protocol enum",
                    "unrecognized external variant",
                ));
            }
        }
        Ok(output)
    }

    fn response_tool(
        &mut self,
        start: ToolStart,
    ) -> Result<Vec<claude::StreamEvent>, TransformError> {
        let ToolStart {
            item_id,
            id,
            name,
            input,
            output_index,
        } = start;
        let index = self.allocate();
        let mut output = self.block_start(
            index,
            claude::ResponseContentBlock::ToolUse(crate::wire!(claude::ResponseToolUseBlock {
                id,
                input: Default::default(),
                name,
                type_: claude::ToolUseBlockType::ToolUse,
                caller: None,
                rest: Default::default(),
            })),
        )?;
        self.response_indices.insert((item_id, None), index);
        self.response_tool_inputs.insert(index, input.clone());
        self.response_output_indices
            .entry(output_index)
            .or_default()
            .push(index);
        self.has_tool = true;
        if !input.is_empty() {
            output.push(self.input_delta(index, input)?);
        }
        Ok(output)
    }
}