gproxy-protocol 4.1.0

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use crate::transform::{Report, TransformError};
use crate::wire::{gemini, openai};
use base64::{Engine as _, engine::general_purpose::STANDARD};

pub(crate) fn converted<T>(value: T, report: Report) -> crate::transform::Converted<T> {
    crate::transform::Converted { value, report }
}

pub(crate) fn model_resource(value: &str) -> Result<String, TransformError> {
    if value.is_empty() || value == "models/" {
        return Err(TransformError::missing_metadata("target_model"));
    }
    if value.starts_with("models/") {
        Ok(value.to_owned())
    } else {
        Ok(format!("models/{value}"))
    }
}

pub(crate) fn target_model_name(value: &str) -> Result<String, TransformError> {
    Ok(value.to_owned())
}

pub(crate) fn source_model_resource(value: &str) -> Result<String, TransformError> {
    model_resource(value)
}

pub(crate) fn text_content(value: String) -> gemini::content::Content {
    gemini::content::Content {
        parts: Some(vec![gemini::content::Part {
            thought: None,
            thought_signature: None,
            part_metadata: None,
            media_resolution: None,
            media_processing: None,
            audio_transcription: None,
            speech_metadata: None,
            text: Some(value),
            inline_data: None,
            function_call: None,
            function_response: None,
            file_data: None,
            executable_code: None,
            code_execution_result: None,
            tool_call: None,
            tool_response: None,
            video_metadata: None,
            rest: Default::default(),
        }]),
        role: None,
        rest: Default::default(),
    }
}

pub(crate) fn content_text(content: gemini::content::Content) -> Result<String, TransformError> {
    Ok(content
        .parts
        .unwrap_or_default()
        .into_iter()
        .filter_map(|part| part.text)
        .collect::<Vec<_>>()
        .join(""))
}

pub(crate) fn dimensions(value: Option<i64>) -> Result<Option<i64>, TransformError> {
    Ok(value)
}

pub(crate) fn gemini_dimensions(
    body: &gemini::embeddings::EmbedContentRequestBody,
) -> Result<Option<i64>, TransformError> {
    let top = dimensions(body.output_dimensionality)?;
    let config = body
        .embed_content_config
        .as_ref()
        .and_then(|config| config.output_dimensionality);
    let config = dimensions(config)?;
    match (top, config) {
        (Some(top), Some(config)) if top != config => Err(TransformError::new(
            crate::transform::TransformErrorKind::Conflict,
            "embedding.output_dimensionality",
            "top-level and config dimensions disagree",
        )),
        (Some(value), _) | (_, Some(value)) => Ok(Some(value)),
        (None, None) => Ok(None),
    }
}

pub(crate) fn decode_base64(value: &str) -> Result<Vec<serde_json::Number>, TransformError> {
    let bytes = STANDARD.decode(value).map_err(|error| {
        TransformError::shape(
            "embedding.base64",
            format!("invalid base64 vector: {error}"),
        )
    })?;

    bytes
        .chunks(4)
        .map(|chunk| {
            let value = f32::from_le_bytes(chunk.try_into().map_err(|_| {
                TransformError::invalid_result("embedding.base64", "incomplete float32 value")
            })?);
            serde_json::Number::from_f64(f64::from(value)).ok_or_else(|| {
                TransformError::invalid_result(
                    "embedding.base64",
                    "vector contains non-finite value",
                )
            })
        })
        .collect()
}

pub(crate) fn vector_values(
    vector: openai::embeddings::EmbeddingVector,
) -> Result<Vec<serde_json::Number>, TransformError> {
    match vector {
        openai::embeddings::EmbeddingVector::Floats(values) => Ok(values),
        openai::embeddings::EmbeddingVector::Base64(value) => decode_base64(&value),
    }
}

pub(crate) fn encode_base64(values: &[serde_json::Number]) -> Result<String, TransformError> {
    let mut bytes = Vec::with_capacity(values.len() * 4);
    for value in values {
        let value = value.as_f64().ok_or_else(|| {
            TransformError::invalid_result("embedding.values", "value is not a JSON number")
        })?;
        let value = value as f32;
        if !value.is_finite() {
            return Err(TransformError::invalid_result(
                "embedding.values",
                "vector contains non-finite value",
            ));
        }
        bytes.extend_from_slice(&value.to_le_bytes());
    }
    Ok(STANDARD.encode(bytes))
}