gestura_core/
openai_compat.rs

1//! OpenAI API compatibility helpers.
2//!
3//! Some OpenAI(-compatible) models reject non-default parameters. We defensively detect
4//! those errors and allow higher layers to retry with a reduced request body.
5
6/// Returns true if an OpenAI(-compatible) error response indicates the `temperature`
7/// parameter is unsupported and callers should retry without it.
8///
9/// This is intentionally tolerant:
10/// - checks `status_code == 400`
11/// - prefers structured JSON fields (`error.param`, `error.code`)
12/// - falls back to substring matching on `error.message`
13pub fn should_retry_without_temperature(status_code: u16, body_text: &str) -> bool {
14    if status_code != 400 {
15        return false;
16    }
17
18    let Ok(v) = serde_json::from_str::<serde_json::Value>(body_text) else {
19        return body_text.to_ascii_lowercase().contains("temperature")
20            && body_text.to_ascii_lowercase().contains("unsupported");
21    };
22
23    let err = &v["error"];
24    let param = err["param"].as_str().unwrap_or_default();
25    let code = err["code"].as_str().unwrap_or_default();
26    let msg = err["message"]
27        .as_str()
28        .unwrap_or_default()
29        .to_ascii_lowercase();
30
31    (param == "temperature" && code == "unsupported_value")
32        || (msg.contains("temperature")
33            && (msg.contains("unsupported") || msg.contains("only the default")))
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn detects_structured_temperature_unsupported_error() {
42        let body = r#"{
43          \"error\": {
44            \"message\": \"Unsupported value: 'temperature' does not support 0.2 with this model. Only the default (1) value is supported.\",
45            \"type\": \"invalid_request_error\",
46            \"param\": \"temperature\",
47            \"code\": \"unsupported_value\"
48          }
49        }"#;
50        assert!(should_retry_without_temperature(400, body));
51    }
52
53    #[test]
54    fn does_not_retry_on_non_400() {
55        let body = r#"{\"error\":{\"param\":\"temperature\",\"code\":\"unsupported_value\"}}"#;
56        assert!(!should_retry_without_temperature(401, body));
57    }
58
59    #[test]
60    fn tolerant_to_non_json_body() {
61        let body = "Unsupported value: temperature";
62        assert!(should_retry_without_temperature(400, body));
63    }
64}