feat: universal LLM gateway with embedded multilingual admin console
- OpenAI-compatible gateway with intelligent routing via cortiq-router (allaigate) - Embedded build-free admin SPA at /admin: 7 languages (en/ru/de/fr/es/zh/tr), light/dark themes, hot config reload (no restart) - Admin API, per-request statistics + Prometheus /metrics, secret store, playground - English documentation (primary) with Russian translations (*.ru.md)
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
//! Unified gateway error type + rendering into an OpenAI-compatible envelope
|
||||
//! `{"error": {message, type, code, param}}` that SDK clients parse natively.
|
||||
//! See docs/PROTOCOLS.md §8.5.
|
||||
|
||||
use axum::{http::StatusCode, response::IntoResponse, Json};
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum GatewayError {
|
||||
#[error("{0}")]
|
||||
InvalidRequest(String),
|
||||
#[error("authentication failed: {0}")]
|
||||
Unauthorized(String),
|
||||
#[error("rate limit exceeded")]
|
||||
RateLimited { retry_after_secs: u64 },
|
||||
#[error("upstream unavailable: {0}")]
|
||||
UpstreamUnavailable(String),
|
||||
#[error("internal error: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
impl GatewayError {
|
||||
fn parts(&self) -> (StatusCode, &'static str, &'static str) {
|
||||
match self {
|
||||
Self::InvalidRequest(_) => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
"invalid_request_error",
|
||||
"invalid_request",
|
||||
),
|
||||
Self::Unauthorized(_) => (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"authentication_error",
|
||||
"unauthorized",
|
||||
),
|
||||
Self::RateLimited { .. } => (
|
||||
StatusCode::TOO_MANY_REQUESTS,
|
||||
"rate_limit_error",
|
||||
"rate_limited",
|
||||
),
|
||||
Self::UpstreamUnavailable(_) => (
|
||||
StatusCode::BAD_GATEWAY,
|
||||
"upstream_unavailable",
|
||||
"no_healthy_model",
|
||||
),
|
||||
Self::Internal(_) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"internal_error",
|
||||
"internal",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for GatewayError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let (status, typ, code) = self.parts();
|
||||
let body = Json(json!({
|
||||
"error": {
|
||||
"message": self.to_string(),
|
||||
"type": typ,
|
||||
"code": code,
|
||||
"param": serde_json::Value::Null,
|
||||
}
|
||||
}));
|
||||
let mut resp = (status, body).into_response();
|
||||
if let Self::RateLimited { retry_after_secs } = self {
|
||||
resp.headers_mut()
|
||||
.insert("retry-after", retry_after_secs.to_string().parse().unwrap());
|
||||
}
|
||||
resp
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, GatewayError>;
|
||||
Reference in New Issue
Block a user