gproxy-protocol 1.0.20

Wire-format types and cross-protocol transforms for Claude, OpenAI, and Gemini LLM APIs.
Documentation
use http::StatusCode;

use crate::claude::types::{BetaError, BetaErrorResponse, BetaModelInfo};
use crate::gemini::types::{GeminiApiError, GeminiApiErrorResponse, GeminiModelInfo};

fn ensure_models_prefix(value: &str) -> String {
    if value.starts_with("models/") {
        value.to_string()
    } else {
        format!("models/{value}")
    }
}

fn gemini_status_from_claude_error(error: &BetaError) -> Option<String> {
    let status = match error {
        BetaError::InvalidRequest(_) => "INVALID_ARGUMENT",
        BetaError::Authentication(_) => "UNAUTHENTICATED",
        BetaError::Billing(_) => "FAILED_PRECONDITION",
        BetaError::Permission(_) => "PERMISSION_DENIED",
        BetaError::NotFound(_) => "NOT_FOUND",
        BetaError::RateLimit(_) => "RESOURCE_EXHAUSTED",
        BetaError::GatewayTimeout(_) => "DEADLINE_EXCEEDED",
        BetaError::Api(_) => "INTERNAL",
        BetaError::Overloaded(_) => "UNAVAILABLE",
    };
    Some(status.to_string())
}

fn claude_error_message(error: &BetaError) -> String {
    match error {
        BetaError::InvalidRequest(err) => err.message.clone(),
        BetaError::Authentication(err) => err.message.clone(),
        BetaError::Billing(err) => err.message.clone(),
        BetaError::Permission(err) => err.message.clone(),
        BetaError::NotFound(err) => err.message.clone(),
        BetaError::RateLimit(err) => err.message.clone(),
        BetaError::GatewayTimeout(err) => err.message.clone(),
        BetaError::Api(err) => err.message.clone(),
        BetaError::Overloaded(err) => err.message.clone(),
    }
}

pub fn gemini_model_info_from_claude_model(model: BetaModelInfo) -> GeminiModelInfo {
    GeminiModelInfo {
        name: ensure_models_prefix(&model.id),
        base_model_id: Some(model.id),
        version: None,
        display_name: if model.display_name.is_empty() {
            None
        } else {
            Some(model.display_name)
        },
        description: None,
        input_token_limit: None,
        output_token_limit: None,
        supported_generation_methods: None,
        thinking: None,
        temperature: None,
        max_temperature: None,
        top_p: None,
        top_k: None,
    }
}

pub fn gemini_error_response_from_claude(
    status_code: StatusCode,
    body: BetaErrorResponse,
) -> GeminiApiErrorResponse {
    GeminiApiErrorResponse {
        error: GeminiApiError {
            code: i32::from(status_code.as_u16()),
            message: claude_error_message(&body.error),
            status: gemini_status_from_claude_error(&body.error),
            details: None,
        },
    }
}