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

@@ -60,6 +60,21 @@ pub struct PendingToolResult {
pub result: serde_json::Value,
}
/// An upstream error captured from Google's API response.
/// Stored by the MITM proxy so API handlers can return it to the client
/// instead of hanging forever waiting for a response that won't come.
#[derive(Debug, Clone)]
pub struct UpstreamError {
/// HTTP status code from Google (e.g. 400, 429, 500).
pub status: u16,
/// Raw error body from Google (usually JSON).
pub body: String,
/// Parsed error message, if available.
pub message: Option<String>,
/// Google error status string (e.g. "INVALID_ARGUMENT", "RESOURCE_EXHAUSTED").
pub error_status: Option<String>,
}
/// A pending image to inject via MITM into the Google API request.
/// The LS doesn't forward images from our SendUserCascadeMessage proto,
/// so we inject them directly at the MITM layer.
@@ -152,6 +167,10 @@ pub struct MitmStore {
// ── Pending image for MITM injection ─────────────────────────────────
/// Image to inject into the next Google API request via MITM.
pending_image: Arc<RwLock<Option<PendingImage>>>,
// ── Upstream error capture ───────────────────────────────────────────
/// Error from Google's API, captured by MITM for forwarding to client.
upstream_error: Arc<RwLock<Option<UpstreamError>>>,
}
/// Aggregate statistics across all intercepted traffic.
@@ -197,6 +216,7 @@ impl MitmStore {
generation_params: Arc::new(RwLock::new(None)),
captured_grounding: Arc::new(RwLock::new(None)),
pending_image: Arc::new(RwLock::new(None)),
upstream_error: Arc::new(RwLock::new(None)),
}
}
@@ -534,4 +554,21 @@ impl MitmStore {
pub async fn take_pending_image(&self) -> Option<PendingImage> {
self.pending_image.write().await.take()
}
// ── Upstream error capture ───────────────────────────────────────────
/// Store an upstream error from Google's API.
pub async fn set_upstream_error(&self, error: UpstreamError) {
*self.upstream_error.write().await = Some(error);
}
/// Take (consume) captured upstream error.
pub async fn take_upstream_error(&self) -> Option<UpstreamError> {
self.upstream_error.write().await.take()
}
/// Clear any stored upstream error.
pub async fn clear_upstream_error(&self) {
*self.upstream_error.write().await = None;
}
}