mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Scope model catalog caches to the current provider and auth identity (#43906)
## Why Switching providers or authentication could reuse a previous identity's model catalog, including its default service tier. An in-flight refresh could also overwrite a newer account's catalog after cache storage completed. ## What changed - Require a matching provider and auth identity for disk and in-memory catalog reuse. Treat legacy entries without an identity as cache misses and fall back to bundled metadata when the in-memory identity no longer matches. - Recheck identity after fetching and storing a catalog before publishing it. - Renew cache freshness only when the client version, identity, and ETag match the stored entry. - Update cache fixtures to include the configured provider and auth identity. ## Testing Add regression coverage for provider and auth switches, credential rotation, legacy and mismatched cache entries, conditional TTL renewal, and an account switch during cache storage. Verify that API-key requests do not inherit a ChatGPT catalog's default `service_tier`, while explicit tier selection still works. GitOrigin-RevId: e15d7ffff238d951d7a1c6a68381cbb479994146
This commit is contained in:
1 parent
4fd2c460dd
commit
f046cf35df
35 files changed
+733
-143
No files matched your search
@@ -5,6 +5,7 @@ mod models_endpoint;
|
||||
mod models_identity;
|
||||
mod provider;
|
||||
mod shared_state;
|
||||
pub mod test_support;
|
||||
|
||||
pub use amazon_bedrock::is_supported_amazon_bedrock_region;
|
||||
pub use auth::AgentIdentitySessionFallback;
|
||||
|
||||
@@ -456,4 +456,104 @@ mod tests {
|
||||
Some(&"eu".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RotatingAuth(std::sync::atomic::AtomicUsize);
|
||||
|
||||
impl codex_login::ExternalAuth for RotatingAuth {
|
||||
fn resolve(&self) -> codex_login::ExternalAuthFuture<'_, CodexAuth> {
|
||||
Box::pin(async move {
|
||||
let generation = self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
Ok(CodexAuth::from_api_key(&format!("token-{generation}")))
|
||||
})
|
||||
}
|
||||
|
||||
fn refresh(
|
||||
&self,
|
||||
_context: codex_login::ExternalAuthRefreshContext,
|
||||
) -> codex_login::ExternalAuthFuture<'_, CodexAuth> {
|
||||
self.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn command_auth_refresh_fetches_a_catalog_for_the_current_credentials() {
|
||||
use codex_models_manager::manager::ModelsManager;
|
||||
use codex_models_manager::manager::OpenAiModelsManager;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
|
||||
let server = MockServer::start().await;
|
||||
let auth = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("initial"));
|
||||
auth.set_external_auth(Arc::new(RotatingAuth(std::sync::atomic::AtomicUsize::new(
|
||||
0,
|
||||
))))
|
||||
.await
|
||||
.unwrap();
|
||||
let model = codex_protocol::openai_models::ModelInfo {
|
||||
used_fallback_model_metadata: false,
|
||||
..codex_models_manager::model_info::model_info_from_slug("command-auth-model")
|
||||
};
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/models"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(ModelsResponse {
|
||||
models: vec![model.clone()],
|
||||
}))
|
||||
.expect(2)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let mut provider = provider_info_with_command_auth();
|
||||
provider.base_url = Some(server.uri());
|
||||
// Keep this test independent of the residency override exercised in parallel.
|
||||
provider.http_headers = Some(std::collections::HashMap::from([(
|
||||
RESIDENCY_HEADER_NAME.to_string(),
|
||||
"us".into(),
|
||||
)]));
|
||||
let manager = OpenAiModelsManager::new_without_cache(
|
||||
Arc::new(OpenAiModelsEndpoint::new(provider, Some(auth.clone()))),
|
||||
Some(auth.clone()),
|
||||
);
|
||||
let catalog = manager
|
||||
.raw_model_catalog(
|
||||
RefreshStrategy::OnlineIfUncached,
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
catalog
|
||||
.models
|
||||
.iter()
|
||||
.find(|candidate| candidate.slug == model.slug),
|
||||
Some(&model)
|
||||
);
|
||||
auth.auth().await;
|
||||
let bundled = codex_models_manager::bundled_models_response().unwrap();
|
||||
assert_eq!(manager.get_remote_models().await, bundled.models);
|
||||
assert_eq!(manager.try_get_remote_models().unwrap(), bundled.models);
|
||||
assert_eq!(
|
||||
manager
|
||||
.raw_model_catalog(
|
||||
RefreshStrategy::Offline,
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
)
|
||||
.await,
|
||||
bundled
|
||||
);
|
||||
assert_eq!(
|
||||
manager
|
||||
.raw_model_catalog(
|
||||
RefreshStrategy::OnlineIfUncached,
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
)
|
||||
.await,
|
||||
catalog
|
||||
);
|
||||
let requests = server.received_requests().await.unwrap();
|
||||
assert_eq!(
|
||||
requests
|
||||
.iter()
|
||||
.map(|request| request.headers["authorization"].to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["Bearer token-2", "Bearer token-6"]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//! Fixtures for integration tests that seed the provider's model cache.
|
||||
|
||||
use codex_login::CodexAuth;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_models_manager::cache::ModelsCacheEntry;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
|
||||
/// Constructs a cache fixture matching the supplied test provider and authentication.
|
||||
pub fn models_cache_entry(
|
||||
provider_info: &ModelProviderInfo,
|
||||
auth: Option<&CodexAuth>,
|
||||
models: Vec<ModelInfo>,
|
||||
) -> ModelsCacheEntry {
|
||||
ModelsCacheEntry {
|
||||
fetched_at: std::time::SystemTime::now().into(),
|
||||
etag: None,
|
||||
client_version: Some(codex_models_manager::client_version_to_whole()),
|
||||
identity: crate::models_identity::identity(provider_info, auth).ok(),
|
||||
models,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user