gproxy-protocol 4.0.3

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use super::supplement::{ClaudeModelSupplement, Converted};
use crate::transform::{Report, TransformError};
use crate::wire::{claude::models as claude_models, openai::models as openai_models};
use time::{OffsetDateTime, format_description::well_known::Rfc3339};

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

pub(crate) fn require<T>(value: Option<T>, field: &str) -> Result<T, TransformError> {
    value.ok_or_else(|| TransformError::missing_metadata(field))
}

pub(crate) fn non_empty(value: &str, field: &str) -> Result<String, TransformError> {
    if value.is_empty() {
        Err(TransformError::missing_metadata(field))
    } else {
        Ok(value.to_owned())
    }
}

pub(crate) fn choose<T: PartialEq>(
    source: Option<T>,
    supplement: Option<T>,
    field: &str,
) -> Result<T, TransformError> {
    match (source, supplement) {
        (Some(source), Some(supplement)) if source != supplement => Err(TransformError::new(
            crate::transform::TransformErrorKind::Conflict,
            field,
            "source and supplied metadata disagree",
        )),
        (Some(source), _) => Ok(source),
        (None, Some(supplement)) => Ok(supplement),
        (None, None) => Err(TransformError::missing_metadata(field)),
    }
}

pub(crate) fn iso_to_epoch_with_loss(value: &str) -> Result<(i64, bool), TransformError> {
    OffsetDateTime::parse(value, &Rfc3339)
        .map(|date| {
            (
                date.unix_timestamp(),
                date.unix_timestamp_nanos() % 1_000_000_000 != 0,
            )
        })
        .map_err(|error| {
            TransformError::shape(
                "model.created_at",
                format!("invalid RFC3339 timestamp: {error}"),
            )
        })
}

pub(crate) fn parse_iso(value: &str) -> Result<OffsetDateTime, TransformError> {
    OffsetDateTime::parse(value, &Rfc3339).map_err(|error| {
        TransformError::shape(
            "model.created_at",
            format!("invalid RFC3339 timestamp: {error}"),
        )
    })
}

pub(crate) fn epoch_to_iso(value: i64) -> Result<String, TransformError> {
    OffsetDateTime::from_unix_timestamp(value)
        .map_err(|error| TransformError::shape("model.created", error.to_string()))?
        .format(&Rfc3339)
        .map_err(|error| TransformError::shape("model.created", error.to_string()))
}

pub(crate) fn openai_model_id(value: &str) -> Result<String, TransformError> {
    if value.is_empty() {
        Err(TransformError::shape("model.id", "model id is empty"))
    } else {
        Ok(value.to_owned())
    }
}

pub(crate) fn gemini_name(value: &str) -> Result<String, TransformError> {
    if value.is_empty() {
        return Err(TransformError::shape("model.name", "model id is empty"));
    }
    if value.starts_with("models/") {
        if value.len() == "models/".len() {
            return Err(TransformError::shape("model.name", "empty models resource"));
        }
        Ok(value.to_owned())
    } else {
        Ok(format!("models/{value}"))
    }
}

pub(crate) fn bare_gemini_id(value: &str) -> Result<String, TransformError> {
    let Some(id) = value.strip_prefix("models/") else {
        return Err(TransformError::shape(
            "model.name",
            "Gemini model resource must begin with models/",
        ));
    };
    if id.is_empty() {
        Err(TransformError::shape("model.name", "empty models resource"))
    } else {
        Ok(id.to_owned())
    }
}

pub(crate) fn openai_model(id: String, created: i64, owned_by: String) -> openai_models::Model {
    openai_models::Model::builder(
        id,
        Some(created),
        openai_models::ModelObject::Model,
        Some(owned_by),
    )
    .build()
}

pub(crate) fn claude_model(
    id: String,
    created_at: String,
    display_name: String,
    max_input_tokens: i64,
    max_tokens: i64,
    supplement: ClaudeModelSupplement,
) -> claude_models::ModelInfo {
    claude_models::ModelInfo {
        id,
        allowed_fallback_models: supplement.allowed_fallback_models,
        capabilities: supplement.capabilities.into_wire(),
        created_at,
        display_name,
        max_input_tokens,
        max_tokens,
        type_: claude_models::ModelType::Model,
        rest: Default::default(),
    }
}

pub(crate) fn merge_report(target: &mut Report, source: Report) {
    target.diagnostics.extend(source.diagnostics);
}

pub(crate) fn report_openai_loss(report: &mut Report) {
    report.omitted("model.owned_by", "target dialect has no owner field");
}

pub(crate) fn report_claude_loss(report: &mut Report) {
    report.omitted(
        "model.capabilities",
        "target dialect has no capability object",
    );
    report.omitted(
        "model.allowed_fallback_models",
        "target dialect has no fallback list",
    );
}

pub(crate) fn report_gemini_loss(report: &mut Report) {
    report.omitted(
        "model.base_model_id",
        "OpenAI model objects have no base-model id",
    );
    report.omitted(
        "model.version",
        "OpenAI model objects have no model version",
    );
    report.omitted(
        "model.temperature",
        "OpenAI model objects have no temperature field",
    );
    report.omitted(
        "model.max_temperature",
        "OpenAI model objects have no max_temperature field",
    );
    report.omitted("model.top_p", "OpenAI model objects have no top_p field");
    report.omitted("model.top_k", "OpenAI model objects have no top_k field");
}