gproxy-protocol 4.1.14

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use super::common::invalid;
use crate::{
    Dialect,
    transform::{
        TransformError,
        identity::{IdentityFlow, IdentityRole, SourceIdentity, TargetIdPolicy},
    },
    wire::{gemini as g, openai::responses::input as r},
};
use std::collections::BTreeSet;

pub(super) fn response_id(
    flow: &mut IdentityFlow,
    policy: &TargetIdPolicy,
    source: Dialect,
    id: Option<String>,
) -> Result<String, TransformError> {
    flow.resolve_or_allocate(
        IdentityRole::Response,
        SourceIdentity::new(source, id, 0),
        policy,
    )
    .map(|h| h.emitted_id)
    .map_err(|e| invalid(e.to_string()))
}

pub(super) fn call(
    part: &mut g::Part,
    original: &r::FunctionCall,
    index: i64,
    allocator: (&mut IdentityFlow, &TargetIdPolicy),
    used: &mut BTreeSet<String>,
) -> Result<(), TransformError> {
    let (flow, policy) = allocator;
    let target = part
        .function_call
        .as_mut()
        .ok_or_else(|| invalid("missing projected function"))?;
    let handle = flow
        .resolve_or_allocate(
            IdentityRole::ToolCall,
            SourceIdentity::new(
                Dialect::OpenAi,
                Some(original.call_id.clone()),
                index as u64,
            ),
            policy,
        )
        .map_err(|e| invalid(e.to_string()))?;
    if !used.insert(handle.emitted_id.clone()) {
        return Err(invalid("projected function ID collides"));
    }
    target.id = Some(handle.emitted_id);
    Ok(())
}

pub(super) fn normalize_text(body: &mut g::GenerateContentResponseBody) {
    for candidate in body.candidates.iter_mut().flatten() {
        if let Some(parts) = candidate.content.as_mut().and_then(|c| c.parts.as_mut()) {
            let mut out: Vec<g::Part> = Vec::new();
            for part in std::mem::take(parts) {
                if plain(&part)
                    && let Some(previous) = out.last_mut()
                    && plain(previous)
                    && previous.thought == part.thought
                {
                    previous
                        .text
                        .as_mut()
                        .expect("plain")
                        .push_str(part.text.as_deref().expect("plain"));
                } else {
                    out.push(part);
                }
            }
            *parts = out;
        }
    }
}

fn plain(p: &g::Part) -> bool {
    p.text.is_some()
        && p.thought_signature.is_none()
        && p.function_call.is_none()
        && p.function_response.is_none()
        && p.inline_data.is_none()
        && p.file_data.is_none()
        && p.executable_code.is_none()
        && p.code_execution_result.is_none()
        && p.tool_call.is_none()
        && p.tool_response.is_none()
        && p.video_metadata.is_none()
        && p.media_processing.is_none()
        && p.audio_transcription.is_none()
        && p.speech_metadata.is_none()
        && p.part_metadata.is_none()
        && p.media_resolution.is_none()
}