Skip to main content

inference_runtime_gemini/
error.rs

1use serde::Deserialize;
2
3use inference_core::error::InferenceError;
4use inference_core::runtime::ProviderKind;
5use inference_remote_core::classify::{classify_http_status, parse_retry_after};
6
7#[derive(Debug, Deserialize)]
8struct ErrorEnvelope {
9    error: ErrorBody,
10}
11
12#[derive(Debug, Deserialize)]
13struct ErrorBody {
14    #[serde(default)]
15    message: String,
16    #[serde(default)]
17    status: String,
18}
19
20pub fn classify_gemini_error(
21    status: u16,
22    retry_after_header: Option<&str>,
23    body: Option<String>,
24) -> InferenceError {
25    let retry_after = parse_retry_after(retry_after_header);
26
27    if let Some(body_str) = body.as_deref() {
28        if let Ok(env) = serde_json::from_str::<ErrorEnvelope>(body_str) {
29            // Gemini returns `RESOURCE_EXHAUSTED` for quota.
30            if env.error.status == "RESOURCE_EXHAUSTED" {
31                return InferenceError::RateLimited {
32                    provider: ProviderKind::Gemini,
33                    retry_after,
34                };
35            }
36            // `FAILED_PRECONDITION` covers safety/policy and a few
37            // other unrelated cases — treat the safety subset as
38            // ContentFiltered when the message hints at it.
39            let lower = env.error.message.to_lowercase();
40            if lower.contains("safety") || lower.contains("blocked") {
41                return InferenceError::ContentFiltered {
42                    reason: env.error.message,
43                };
44            }
45        }
46    }
47
48    classify_http_status(ProviderKind::Gemini, status, retry_after, body)
49}