Improve Codex model fetching

This commit is contained in:
fawney19
2026-05-04 01:16:56 +08:00
parent fb642f7d62
commit 1d62722d47
5 changed files with 363 additions and 48 deletions

View File

@@ -67,6 +67,21 @@ struct ProviderQueryKeyFetchResult {
has_success: bool,
}
fn provider_query_codex_preset_fallback(
provider: &StoredProviderCatalogProvider,
) -> Option<ProviderQueryKeyFetchResult> {
if !provider.provider_type.trim().eq_ignore_ascii_case("codex") {
return None;
}
let models = preset_models_for_provider(&provider.provider_type)?;
Some(ProviderQueryKeyFetchResult {
models: aggregate_models_for_cache(&models),
error: None,
from_cache: false,
has_success: true,
})
}
#[derive(Debug, Clone)]
struct ProviderQueryTestCandidate {
endpoint: StoredProviderCatalogEndpoint,
@@ -1664,6 +1679,9 @@ async fn provider_query_fetch_models_for_key(
Ok(outcome) => outcome,
Err(err) => {
all_errors.push(err);
if let Some(fallback) = provider_query_codex_preset_fallback(provider) {
return Ok(fallback);
}
return Ok(ProviderQueryKeyFetchResult {
models: Vec::new(),
error: Some(all_errors.join("; ")),
@@ -1685,6 +1703,12 @@ async fn provider_query_fetch_models_for_key(
.await;
}
if unique_models.is_empty() && !all_errors.is_empty() {
if let Some(fallback) = provider_query_codex_preset_fallback(provider) {
return Ok(fallback);
}
}
let mut error = if all_errors.is_empty() {
None
} else {

View File

@@ -346,6 +346,112 @@ async fn gateway_handles_admin_provider_query_models_with_openai_responses_endpo
execution_runtime_handle.abort();
}
#[tokio::test]
async fn gateway_handles_admin_provider_query_models_falls_back_to_codex_preset_when_token_invalidated(
) {
let execution_runtime_hits = Arc::new(Mutex::new(0usize));
let execution_runtime_hits_clone = Arc::clone(&execution_runtime_hits);
let execution_runtime = Router::new().route(
"/v1/execute/sync",
any(move |Json(plan): Json<ExecutionPlan>| {
let execution_runtime_hits_inner = Arc::clone(&execution_runtime_hits_clone);
async move {
*execution_runtime_hits_inner
.lock()
.expect("mutex should lock") += 1;
assert_eq!(
plan.url,
"https://chatgpt.com/backend-api/codex/models?client_version=0.128.0-alpha.1"
);
Json(json!({
"request_id": "req-provider-query-codex-invalidated",
"status_code": 403,
"headers": {
"content-type": "application/json"
},
"body": {
"json_body": {
"error": {
"message": "Your authentication token has been invalidated. Please sign in again."
}
}
}
}))
}
}),
);
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let mut provider = sample_provider("provider-codex", "Codex", 10);
provider.provider_type = "codex".to_string();
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![sample_endpoint(
"endpoint-codex-responses",
"provider-codex",
"openai:responses",
"https://chatgpt.com/backend-api/codex",
)],
vec![sample_key(
"key-codex-invalidated",
"provider-codex",
"openai:responses",
"invalidated-token",
)],
));
let gateway = build_router_with_state(
build_state_with_execution_runtime_override(execution_runtime_url)
.with_data_state_for_tests(GatewayDataState::with_provider_transport_reader_for_tests(
provider_catalog_repository,
DEVELOPMENT_ENCRYPTION_KEY.to_string(),
)),
);
let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new()
.post(format!("{gateway_url}/api/admin/provider-query/models"))
.header(crate::constants::GATEWAY_HEADER, "rust-phase3b")
.header(TRUSTED_ADMIN_USER_ID_HEADER, "admin-user-123")
.header(TRUSTED_ADMIN_USER_ROLE_HEADER, "admin")
.header(TRUSTED_ADMIN_SESSION_ID_HEADER, "session-123")
.json(&json!({
"provider_id": "provider-codex",
"api_key_id": "key-codex-invalidated"
}))
.send()
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::OK);
let payload: serde_json::Value = response.json().await.expect("json body should parse");
assert_eq!(payload["success"], json!(true));
assert_eq!(payload["data"]["error"], serde_json::Value::Null);
let model_ids = payload["data"]["models"]
.as_array()
.expect("models should be an array")
.iter()
.map(|model| model["id"].as_str().expect("model id"))
.collect::<Vec<_>>();
assert_eq!(
model_ids,
vec![
"gpt-5.3-codex",
"gpt-5.3-codex-spark",
"gpt-5.4",
"gpt-5.4-mini",
"gpt-5.5",
]
);
assert_eq!(
*execution_runtime_hits.lock().expect("mutex should lock"),
1
);
gateway_handle.abort();
execution_runtime_handle.abort();
}
#[tokio::test]
async fn gateway_handles_admin_provider_query_models_respecting_key_api_formats() {
let execution_runtime_hits = Arc::new(Mutex::new(0usize));