feat: propagate Google upstream errors to client

When Google returns an error (400, 429, 500, etc.), the MITM proxy now
captures it and the API handlers return it immediately instead of
hanging until timeout.

- UpstreamError struct stored in MitmStore
- MITM proxy parses Google error JSON (message + status)
- Polling handler checks for upstream errors each cycle
- Streaming handlers emit response.failed / SSE error events
- Error status mapped to OpenAI-style types (invalid_request_error,
  rate_limit_error, authentication_error, server_error, etc.)
- All handlers clear stale errors at request start
This commit is contained in:
Nikketryhard
2026-02-15 18:19:38 -06:00
parent 371c57bab0
commit 2882f7cce2
8 changed files with 195 additions and 14 deletions

View File

@@ -25,6 +25,28 @@ pub(crate) fn err_response(
(status, Json(body)).into_response()
}
/// Convert a MITM-captured upstream error from Google into an HTTP response.
/// Maps Google's HTTP status codes and preserves the error message.
pub(crate) fn upstream_err_response(err: &crate::mitm::store::UpstreamError) -> axum::response::Response {
// Map Google's status code to HTTP status
let status = StatusCode::from_u16(err.status).unwrap_or(StatusCode::BAD_GATEWAY);
// Map Google error status to OpenAI-style error type
let error_type = match err.error_status.as_deref() {
Some("INVALID_ARGUMENT") => "invalid_request_error",
Some("RESOURCE_EXHAUSTED") => "rate_limit_error",
Some("PERMISSION_DENIED") | Some("UNAUTHENTICATED") => "authentication_error",
Some("NOT_FOUND") => "not_found_error",
Some("INTERNAL") | Some("UNAVAILABLE") => "server_error",
_ => "upstream_error",
};
let message = err.message.clone()
.unwrap_or_else(|| format!("Google API returned HTTP {}", err.status));
err_response(status, message, error_type)
}
pub(crate) fn now_unix() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)