gephyr 1.16.18

Gephyr is a headless local AI relay/proxy API handling OpenAI, Claude, and Gemini-compatible APIs
Documentation
use axum::{
    http::{HeaderMap, HeaderValue, StatusCode},
    response::{IntoResponse, Response},
    Json,
};
use serde_json::{json, Value};

pub fn claude_invalid_request_response(
    message: impl Into<String>,
    suggestion: Option<String>,
) -> Response {
    let mut body = json!({
        "type": "error",
        "error": {
            "type": "invalid_request_error",
            "message": message.into()
        }
    });

    if let Some(s) = suggestion {
        if let Some(err) = body.get_mut("error").and_then(Value::as_object_mut) {
            err.insert("suggestion".to_string(), json!(s));
        }
    }

    (StatusCode::BAD_REQUEST, Json(body)).into_response()
}

pub fn claude_prompt_too_long_response(email: &str) -> Response {
    (
        StatusCode::BAD_REQUEST,
        [("X-Account-Email", email)],
        Json(json!({
            "id": "err_prompt_too_long",
            "type": "error",
            "error": {
                "type": "invalid_request_error",
                "message": "Prompt is too long (server-side context limit reached).",
                "suggestion": format!(
                    "Please: 1) Executive '/compact' in Claude Code 2) Reduce conversation history 3) Switch to {} (2M context limit)",
                    crate::proxy::common::model_mapping::MODEL_GEMINI_3_PRO
                )
            }
        })),
    )
        .into_response()
}

pub fn claude_retry_exhausted_response(
    max_attempts: usize,
    last_status: StatusCode,
    last_error: &str,
    last_email: Option<&str>,
    mapped_model: Option<&str>,
) -> Response {
    let mut headers = HeaderMap::new();
    if let Some(email) = last_email {
        if let Ok(v) = HeaderValue::from_str(email) {
            headers.insert("X-Account-Email", v);
        }
    }
    if let Some(model) = mapped_model {
        if let Ok(v) = HeaderValue::from_str(model) {
            headers.insert("X-Mapped-Model", v);
        }
    }

    let error_type = match last_status.as_u16() {
        400 => "invalid_request_error",
        401 => "authentication_error",
        403 => "permission_error",
        429 => "rate_limit_error",
        529 => "overloaded_error",
        _ => "api_error",
    };
    let response_status = if last_status.as_u16() == 403 {
        StatusCode::SERVICE_UNAVAILABLE
    } else {
        last_status
    };

    (
        response_status,
        headers,
        Json(json!({
            "type": "error",
            "error": {
                "id": "err_retry_exhausted",
                "type": error_type,
                "message": format!(
                    "All {} attempts failed. Last status: {}. Error: {}",
                    max_attempts, last_status, last_error
                )
            }
        })),
    )
        .into_response()
}

pub fn accounts_exhausted_text_response(
    last_error: &str,
    last_email: Option<&str>,
    mapped_model: Option<&str>,
) -> Response {
    let mut headers = HeaderMap::new();
    if let Some(email) = last_email {
        if let Ok(v) = HeaderValue::from_str(email) {
            headers.insert("X-Account-Email", v);
        }
    }
    if let Some(model) = mapped_model {
        if let Ok(v) = HeaderValue::from_str(model) {
            headers.insert("X-Mapped-Model", v);
        }
    }

    (
        StatusCode::TOO_MANY_REQUESTS,
        headers,
        format!("All accounts exhausted. Last error: {}", last_error),
    )
        .into_response()
}

pub fn stream_collection_error_response(error: &str) -> Response {
    (
        StatusCode::INTERNAL_SERVER_ERROR,
        format!("Stream collection error: {}", error),
    )
        .into_response()
}

pub fn parse_error_response(error: &str, mapped_model: Option<&str>) -> Response {
    let mut headers = HeaderMap::new();
    if let Some(model) = mapped_model {
        if let Ok(v) = HeaderValue::from_str(model) {
            headers.insert("X-Mapped-Model", v);
        }
    }
    (
        StatusCode::BAD_GATEWAY,
        headers,
        format!("Parse error: {}", error),
    )
        .into_response()
}

pub fn openai_upstream_error_response(
    status: StatusCode,
    error_text: &str,
    account_email: Option<&str>,
    mapped_model: Option<&str>,
) -> Response {
    let mut headers = HeaderMap::new();
    if let Some(email) = account_email {
        if let Ok(v) = HeaderValue::from_str(email) {
            headers.insert("X-Account-Email", v);
        }
    }
    if let Some(model) = mapped_model {
        if let Ok(v) = HeaderValue::from_str(model) {
            headers.insert("X-Mapped-Model", v);
        }
    }

    (
        status,
        headers,
        Json(json!({
            "error": {
                "message": error_text,
                "type": "upstream_error",
                "code": status.as_u16()
            }
        })),
    )
        .into_response()
}

pub fn gemini_upstream_error_response(
    status: StatusCode,
    error_text: &str,
    account_email: Option<&str>,
    mapped_model: Option<&str>,
) -> Response {
    let mut headers = HeaderMap::new();
    if let Some(email) = account_email {
        if let Ok(v) = HeaderValue::from_str(email) {
            headers.insert("X-Account-Email", v);
        }
    }
    if let Some(model) = mapped_model {
        if let Ok(v) = HeaderValue::from_str(model) {
            headers.insert("X-Mapped-Model", v);
        }
    }

    (
        status,
        headers,
        Json(json!({
            "error": {
                "code": status.as_u16(),
                "message": error_text,
                "status": "UPSTREAM_ERROR"
            }
        })),
    )
        .into_response()
}

pub fn text_error_response(
    status: StatusCode,
    message: &str,
    account_email: Option<&str>,
    mapped_model: Option<&str>,
) -> Response {
    let mut headers = HeaderMap::new();
    if let Some(email) = account_email {
        if let Ok(v) = HeaderValue::from_str(email) {
            headers.insert("X-Account-Email", v);
        }
    }
    if let Some(model) = mapped_model {
        if let Ok(v) = HeaderValue::from_str(model) {
            headers.insert("X-Mapped-Model", v);
        }
    }

    (status, headers, message.to_string()).into_response()
}