fix: add llm request lifecycle timeouts

This commit is contained in:
2026-05-28 02:42:31 +09:00
parent 647223eb32
commit 9cd776eaec
6 changed files with 231 additions and 56 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ reqwest = { version = "0.13", features = ["json", "native-tls"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["sync", "fs", "rt"] }
tokio = { workspace = true, features = ["sync", "fs", "rt", "time"] }
toml = { workspace = true }
tracing = { workspace = true }
+43 -7
View File
@@ -4,11 +4,13 @@
//! 401 + `error.code` で永続失敗を分類する。
use serde::{Deserialize, Serialize};
use std::time::Duration;
use super::error::{CodexAuthError, PermanentReason};
pub const CLIENT_ID: &str = "app_EMoamEEZ73f0CkXaXp7hrann";
pub const REFRESH_URL: &str = "https://auth.openai.com/oauth/token";
pub const DEFAULT_REFRESH_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Serialize)]
struct RefreshRequest<'a> {
@@ -41,13 +43,15 @@ pub async fn request_refresh(
grant_type: "refresh_token",
refresh_token,
};
let response = client
.post(endpoint)
.header("Content-Type", "application/json")
.json(&body)
.send()
.await
.map_err(|e| CodexAuthError::RefreshTransient(format!("send: {e}")))?;
let response = response_with_timeout(
client
.post(endpoint)
.header("Content-Type", "application/json")
.json(&body)
.send(),
DEFAULT_REFRESH_TIMEOUT,
)
.await?;
let status = response.status();
if status.is_success() {
@@ -68,6 +72,21 @@ pub async fn request_refresh(
}
}
async fn response_with_timeout(
future: impl std::future::Future<Output = Result<reqwest::Response, reqwest::Error>>,
timeout: Duration,
) -> Result<reqwest::Response, CodexAuthError> {
tokio::time::timeout(timeout, future)
.await
.map_err(|_| {
CodexAuthError::RefreshTransient(format!(
"codex_oauth_refresh timed out after {}s",
timeout.as_secs()
))
})?
.map_err(|e| CodexAuthError::RefreshTransient(format!("send: {e}")))
}
fn classify_permanent(body: &str) -> (PermanentReason, String) {
let code = extract_error_code(body);
let reason = match code.as_deref() {
@@ -107,6 +126,23 @@ fn extract_error_code(body: &str) -> Option<String> {
mod tests {
use super::*;
#[tokio::test]
async fn refresh_response_timeout_is_transient() {
let err = match response_with_timeout(
std::future::pending::<Result<reqwest::Response, reqwest::Error>>(),
Duration::from_millis(5),
)
.await
{
Ok(_) => panic!("expected refresh timeout"),
Err(err) => err,
};
assert!(
matches!(err, CodexAuthError::RefreshTransient(message) if message.contains("timed out"))
);
}
#[test]
fn classify_expired() {
let body = r#"{"error":{"code":"refresh_token_expired"}}"#;