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::gemini::types::{GeminiApiErrorResponse, GeminiModelInfo};
use crate::openai::types::{
    OpenAiApiError, OpenAiApiErrorResponse, OpenAiModel, OpenAiModelObject,
};

pub fn strip_models_prefix(value: &str) -> String {
    value.strip_prefix("models/").unwrap_or(value).to_string()
}

fn openai_error_type_from_google_status(status: &str) -> Option<&'static str> {
    match status {
        "INVALID_ARGUMENT" => Some("invalid_request_error"),
        "UNAUTHENTICATED" => Some("authentication_error"),
        "PERMISSION_DENIED" => Some("permission_error"),
        "NOT_FOUND" => Some("not_found_error"),
        "RESOURCE_EXHAUSTED" => Some("rate_limit_error"),
        "DEADLINE_EXCEEDED" => Some("timeout_error"),
        "FAILED_PRECONDITION" => Some("invalid_request_error"),
        "ABORTED" | "INTERNAL" | "UNAVAILABLE" => Some("api_error"),
        _ => None,
    }
}

fn openai_error_type_from_http_status(status_code: StatusCode) -> &'static str {
    match status_code.as_u16() {
        400 | 413 => "invalid_request_error",
        401 => "authentication_error",
        403 => "permission_error",
        404 => "not_found_error",
        429 => "rate_limit_error",
        504 => "timeout_error",
        _ => "api_error",
    }
}

pub fn openai_model_from_gemini_model(model: GeminiModelInfo) -> OpenAiModel {
    let id = model
        .base_model_id
        .as_deref()
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(ToOwned::to_owned)
        .unwrap_or_else(|| strip_models_prefix(&model.name));

    OpenAiModel {
        id,
        created: 0,
        object: OpenAiModelObject::Model,
        owned_by: "google".to_string(),
    }
}

pub fn openai_error_response_from_gemini(
    status_code: StatusCode,
    body: GeminiApiErrorResponse,
) -> OpenAiApiErrorResponse {
    let error = body.error;
    let type_ = error
        .status
        .as_deref()
        .and_then(openai_error_type_from_google_status)
        .unwrap_or_else(|| openai_error_type_from_http_status(status_code));

    OpenAiApiErrorResponse {
        error: OpenAiApiError {
            message: error.message,
            type_: type_.to_string(),
            param: None,
            code: error.status.or(Some(error.code.to_string())),
        },
    }
}