gproxy-protocol 1.0.20

Wire-format types and cross-protocol transforms for Claude, OpenAI, and Gemini LLM APIs.
Documentation
use crate::claude::create_message::response::ClaudeCreateMessageResponse;
use crate::claude::create_message::stream::{
    BetaMessageDeltaUsage, BetaRawContentBlockDelta, BetaRawMessageDelta, ClaudeStreamEvent,
};
use crate::claude::create_message::types::BetaContentBlock;
use crate::transform::utils::TransformError;

fn stream_start_content_block(content_block: &BetaContentBlock) -> BetaContentBlock {
    match content_block {
        BetaContentBlock::Text(block) => {
            BetaContentBlock::Text(crate::claude::create_message::types::BetaTextBlock {
                citations: None,
                text: String::new(),
                type_: block.type_.clone(),
            })
        }
        BetaContentBlock::Thinking(block) => {
            BetaContentBlock::Thinking(crate::claude::create_message::types::BetaThinkingBlock {
                signature: block.signature.clone(),
                thinking: String::new(),
                type_: block.type_.clone(),
            })
        }
        BetaContentBlock::ToolUse(block) => {
            BetaContentBlock::ToolUse(crate::claude::create_message::types::BetaToolUseBlock {
                id: block.id.clone(),
                input: Default::default(),
                name: block.name.clone(),
                type_: block.type_.clone(),
                cache_control: block.cache_control.clone(),
                caller: block.caller.clone(),
            })
        }
        BetaContentBlock::Compaction(block) => BetaContentBlock::Compaction(
            crate::claude::create_message::types::BetaCompactionBlock {
                content: None,
                type_: block.type_.clone(),
                cache_control: block.cache_control.clone(),
            },
        ),
        _ => content_block.clone(),
    }
}

fn push_content_block_delta_events(
    events: &mut Vec<ClaudeStreamEvent>,
    index: u64,
    content_block: &BetaContentBlock,
) {
    match content_block {
        BetaContentBlock::Text(block) => {
            if !block.text.is_empty() {
                events.push(ClaudeStreamEvent::ContentBlockDelta {
                    delta: BetaRawContentBlockDelta::Text {
                        text: block.text.clone(),
                    },
                    index,
                });
            }
            if let Some(citations) = block.citations.as_ref() {
                for citation in citations {
                    events.push(ClaudeStreamEvent::ContentBlockDelta {
                        delta: BetaRawContentBlockDelta::Citations {
                            citation: citation.clone(),
                        },
                        index,
                    });
                }
            }
        }
        BetaContentBlock::Thinking(block) => {
            if !block.thinking.is_empty() {
                events.push(ClaudeStreamEvent::ContentBlockDelta {
                    delta: BetaRawContentBlockDelta::Thinking {
                        thinking: block.thinking.clone(),
                    },
                    index,
                });
            }
            if !block.signature.is_empty() {
                events.push(ClaudeStreamEvent::ContentBlockDelta {
                    delta: BetaRawContentBlockDelta::Signature {
                        signature: block.signature.clone(),
                    },
                    index,
                });
            }
        }
        BetaContentBlock::ToolUse(block) => {
            if !block.input.is_empty()
                && let Ok(input_json) = serde_json::to_string(&block.input)
                && !input_json.is_empty()
                && input_json != "{}"
            {
                events.push(ClaudeStreamEvent::ContentBlockDelta {
                    delta: BetaRawContentBlockDelta::InputJson {
                        partial_json: input_json,
                    },
                    index,
                });
            }
        }
        BetaContentBlock::Compaction(block) if block.content.is_some() => {
            events.push(ClaudeStreamEvent::ContentBlockDelta {
                delta: BetaRawContentBlockDelta::Compaction {
                    content: block.content.clone(),
                },
                index,
            });
        }
        _ => {}
    }
}

pub fn nonstream_to_stream(
    value: ClaudeCreateMessageResponse,
    out: &mut Vec<ClaudeStreamEvent>,
) -> Result<(), TransformError> {
    match value {
        ClaudeCreateMessageResponse::Success { body, .. } => {
            let mut start_message = body.clone();
            start_message.content = Vec::new();
            start_message.context_management = None;
            start_message.stop_reason = None;
            start_message.stop_sequence = None;
            start_message.usage.output_tokens = 0;

            out.push(ClaudeStreamEvent::MessageStart {
                message: start_message,
            });

            for (index, content_block) in body.content.iter().enumerate() {
                let index = index as u64;
                out.push(ClaudeStreamEvent::ContentBlockStart {
                    content_block: stream_start_content_block(content_block),
                    index,
                });

                push_content_block_delta_events(out, index, content_block);

                out.push(ClaudeStreamEvent::ContentBlockStop { index });
            }

            out.push(ClaudeStreamEvent::MessageDelta {
                context_management: body.context_management.clone(),
                delta: BetaRawMessageDelta {
                    container: body.container.clone(),
                    stop_reason: body.stop_reason.clone(),
                    stop_sequence: body.stop_sequence.clone(),
                },
                usage: BetaMessageDeltaUsage {
                    cache_creation_input_tokens: Some(body.usage.cache_creation_input_tokens),
                    cache_read_input_tokens: Some(body.usage.cache_read_input_tokens),
                    input_tokens: Some(body.usage.input_tokens),
                    iterations: Some(body.usage.iterations.clone()),
                    output_tokens: body.usage.output_tokens,
                    server_tool_use: Some(body.usage.server_tool_use.clone()),
                },
            });

            out.push(ClaudeStreamEvent::MessageStop {});

            Ok(())
        }
        ClaudeCreateMessageResponse::Error { body, .. } => {
            out.push(ClaudeStreamEvent::Error { error: body.error });
            Ok(())
        }
    }
}