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

@@ -744,10 +744,29 @@ async fn handle_http_over_tls(
}
headers_parsed = true;
// Log error response bodies for debugging
if resp.code.unwrap_or(0) >= 400 {
let body_preview = String::from_utf8_lossy(&header_buf[hdr_end..]);
warn!(domain, status = resp.code.unwrap_or(0), body = %body_preview, "MITM: upstream error response");
// Capture upstream errors for forwarding to client
let http_status = resp.code.unwrap_or(0) as u16;
if http_status >= 400 {
let body_str = String::from_utf8_lossy(&header_buf[hdr_end..]).to_string();
warn!(domain, status = http_status, body = %body_str, "MITM: upstream error response");
// Parse Google's error JSON: {"error": {"code": N, "message": "...", "status": "..."}}
let (message, error_status) = serde_json::from_str::<serde_json::Value>(&body_str)
.ok()
.and_then(|v| {
let err = v.get("error")?;
let msg = err.get("message").and_then(|m| m.as_str()).map(|s| s.to_string());
let status = err.get("status").and_then(|s| s.as_str()).map(|s| s.to_string());
Some((msg, status))
})
.unwrap_or((None, None));
store.set_upstream_error(super::store::UpstreamError {
status: http_status,
body: body_str,
message,
error_status,
}).await;
}
// Save body for usage parsing