From 81788d3a6440357e33385d0a66f293053aa73cca Mon Sep 17 00:00:00 2001 From: AAEE86 Date: Wed, 23 Sep 2026 16:47:45 +0800 Subject: [PATCH] fix(codex): confine codex profile api to ai_serving root seams --- apps/aether-gateway/src/ai_serving/api.rs | 5 +++ apps/aether-gateway/src/codex_profile.rs | 40 +++++++++---------- .../admin/provider/query/models/mod.rs | 2 +- .../aether-gateway/src/model_fetch/catalog.rs | 27 ++++--------- 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/apps/aether-gateway/src/ai_serving/api.rs b/apps/aether-gateway/src/ai_serving/api.rs index 07bb62146..3225fa7af 100644 --- a/apps/aether-gateway/src/ai_serving/api.rs +++ b/apps/aether-gateway/src/ai_serving/api.rs @@ -72,6 +72,11 @@ pub(crate) use aether_ai_formats::protocol::stream::CanonicalUsage as StreamingC /// Codex client identity accessors re-exported for out-of-crate probe binaries, /// which must reach the runtime profile through this seam. pub use aether_ai_formats::{codex_client_originator, codex_client_user_agent}; +/// Codex 动态客户端画像 API 只允许经此根缝进入 gateway,避免其它模块直接依赖 formats crate。 +pub(crate) use aether_ai_formats::{ + codex_client_profile, codex_client_version, set_codex_cli_version, set_codex_client_profile, + CodexClientProfile, +}; pub(crate) use aether_ai_formats::{CODEX_RESPONSES_LITE_HEADER, UPSTREAM_IS_STREAM_KEY}; pub(crate) fn parse_direct_request_body( diff --git a/apps/aether-gateway/src/codex_profile.rs b/apps/aether-gateway/src/codex_profile.rs index c1ec8664c..aa27cc50c 100644 --- a/apps/aether-gateway/src/codex_profile.rs +++ b/apps/aether-gateway/src/codex_profile.rs @@ -11,6 +11,7 @@ use semver::Version; use serde::{Deserialize, Serialize}; use tracing::{info, warn}; +use crate::ai_serving::api::{codex_client_version, set_codex_cli_version}; use crate::AppState; const CLI_RELEASE_ENDPOINT: &str = "https://registry.npmjs.org/@openai%2Fcodex/latest"; @@ -181,11 +182,8 @@ async fn restore_cached_profile(runtime: &RuntimeState) -> Result<(), ProfileRef }; let cached = serde_json::from_str::(&raw) .map_err(|_| ProfileRefreshError::InvalidMetadata)?; - if let Some(version) = - cached_version_to_restore(&cached, &aether_ai_formats::codex_client_version())? - { - aether_ai_formats::set_codex_cli_version(&version) - .map_err(|_| ProfileRefreshError::InvalidMetadata)?; + if let Some(version) = cached_version_to_restore(&cached, &codex_client_version())? { + set_codex_cli_version(&version).map_err(|_| ProfileRefreshError::InvalidMetadata)?; info!( event_name = "codex_client_profile_restored", version = %version, @@ -216,8 +214,7 @@ where Fut: Future>, { if let Some(version) = fixed_version { - aether_ai_formats::set_codex_cli_version(version) - .map_err(|_| ProfileRefreshError::InvalidMetadata)?; + set_codex_cli_version(version).map_err(|_| ProfileRefreshError::InvalidMetadata)?; return Ok(version.to_owned()); } @@ -230,11 +227,11 @@ where ); } if !refresh_is_enabled { - return Ok(aether_ai_formats::codex_client_version()); + return Ok(codex_client_version()); } let version = fetch_latest().await?; - let current = aether_ai_formats::codex_client_version(); + let current = codex_client_version(); if version_sequence(&version)? < version_sequence(¤t)? { return Err(ProfileRefreshError::Rollback); } @@ -245,8 +242,7 @@ where }; let serialized = serde_json::to_string(&cached).map_err(|_| ProfileRefreshError::InvalidMetadata)?; - aether_ai_formats::set_codex_cli_version(&version) - .map_err(|_| ProfileRefreshError::InvalidMetadata)?; + set_codex_cli_version(&version).map_err(|_| ProfileRefreshError::InvalidMetadata)?; if let Err(error) = runtime .kv_set(PROFILE_CACHE_KEY, serialized, Some(PROFILE_CACHE_TTL)) .await @@ -321,21 +317,25 @@ mod tests { cached_version_to_restore, fixed_version_from, parse_cli_release, refresh_enabled_from, refresh_once_with_fetch, CachedProfile, ProfileRefreshError, PROFILE_CACHE_KEY, }; + use crate::ai_serving::api::{ + codex_client_profile, codex_client_version, set_codex_cli_version, + set_codex_client_profile, CodexClientProfile, + }; static PROFILE_TEST_LOCK: OnceLock> = OnceLock::new(); - struct ProfileRestore(aether_ai_formats::CodexClientProfile); + struct ProfileRestore(CodexClientProfile); impl Drop for ProfileRestore { fn drop(&mut self) { - aether_ai_formats::set_codex_client_profile(self.0.clone()); + set_codex_client_profile(self.0.clone()); } } fn profile_restore_guard() -> (std::sync::MutexGuard<'static, ()>, ProfileRestore) { let lock = PROFILE_TEST_LOCK.get_or_init(|| Mutex::new(())); let guard = lock.lock().expect("profile test lock"); - let restore = ProfileRestore(aether_ai_formats::codex_client_profile()); + let restore = ProfileRestore(codex_client_profile()); (guard, restore) } @@ -419,21 +419,21 @@ mod tests { .unwrap(); assert_eq!(result, "0.200.1"); - assert_eq!(aether_ai_formats::codex_client_version(), "0.200.1"); + assert_eq!(codex_client_version(), "0.200.1"); } #[tokio::test] async fn refresh_failure_keeps_previous_profile() { let (_lock, _restore) = profile_restore_guard(); let runtime = RuntimeState::memory(MemoryRuntimeStateConfig::default()); - let before = aether_ai_formats::codex_client_profile(); + let before = codex_client_profile(); let result = refresh_once_with_fetch(&runtime, None, true, || async { Err(ProfileRefreshError::HttpStatus(503)) }) .await; assert!(matches!(result, Err(ProfileRefreshError::HttpStatus(503)))); - assert_eq!(aether_ai_formats::codex_client_profile(), before); + assert_eq!(codex_client_profile(), before); } #[tokio::test] @@ -450,19 +450,19 @@ mod tests { assert_eq!(result, "0.220.0"); assert!(!fetch_called.load(Ordering::SeqCst)); - assert_eq!(aether_ai_formats::codex_client_version(), "0.220.0"); + assert_eq!(codex_client_version(), "0.220.0"); } #[tokio::test] async fn rollback_is_rejected_without_replacing_profile() { let (_lock, _restore) = profile_restore_guard(); - aether_ai_formats::set_codex_cli_version("0.220.0").unwrap(); + set_codex_cli_version("0.220.0").unwrap(); let runtime = RuntimeState::memory(MemoryRuntimeStateConfig::default()); let result = refresh_once_with_fetch(&runtime, None, true, || async { Ok("0.219.9".to_string()) }) .await; assert!(matches!(result, Err(ProfileRefreshError::Rollback))); - assert_eq!(aether_ai_formats::codex_client_version(), "0.220.0"); + assert_eq!(codex_client_version(), "0.220.0"); } } diff --git a/apps/aether-gateway/src/handlers/admin/provider/query/models/mod.rs b/apps/aether-gateway/src/handlers/admin/provider/query/models/mod.rs index 979dc57aa..e84bb48e8 100644 --- a/apps/aether-gateway/src/handlers/admin/provider/query/models/mod.rs +++ b/apps/aether-gateway/src/handlers/admin/provider/query/models/mod.rs @@ -594,7 +594,7 @@ async fn provider_query_fetch_models_for_key( }); } - let dynamic_client_version = aether_ai_formats::codex_client_version(); + let dynamic_client_version = crate::ai_serving::api::codex_client_version(); let client_version = is_codex.then(|| { codex_catalog .as_ref() diff --git a/apps/aether-gateway/src/model_fetch/catalog.rs b/apps/aether-gateway/src/model_fetch/catalog.rs index 1b16b5a37..65e274e6f 100644 --- a/apps/aether-gateway/src/model_fetch/catalog.rs +++ b/apps/aether-gateway/src/model_fetch/catalog.rs @@ -19,6 +19,8 @@ use sha2::{Digest, Sha256}; use tokio::sync::{Mutex, Semaphore}; use tracing::{debug, info, warn}; +use crate::ai_serving::api::codex_client_version; + const CODEX_CATALOG_SCHEMA_VERSION: u32 = 2; const CODEX_CATALOG_CREDENTIAL_SCOPE_DOMAIN: &str = "aether-codex-catalog-credential-v2"; const CODEX_CLIENT_VERSION_MAX_LEN: usize = 64; @@ -99,7 +101,7 @@ pub(crate) fn normalize_codex_client_version(raw: Option<&str>) -> NormalizedCod used_fallback: false, }, None => NormalizedCodexClientVersion { - value: aether_ai_formats::codex_client_version(), + value: codex_client_version(), used_fallback: true, }, } @@ -1521,7 +1523,7 @@ where .await?; let scope = target.credential_scope()?; let state = runtime.codex_catalog_runtime_state(); - let mut version = Version::parse(&aether_ai_formats::codex_client_version()).ok()?; + let mut version = Version::parse(&codex_client_version()).ok()?; if let Some(recent) = read_recent_codex_catalog_client_version(state, provider_id, key_id, scope).await { @@ -2461,10 +2463,7 @@ mod tests { let initial = read_codex_management_catalog(&runtime, TEST_PROVIDER_ID, TEST_KEY_ID) .await .expect("management context"); - assert_eq!( - initial.client_version, - aether_ai_formats::codex_client_version() - ); + assert_eq!(initial.client_version, codex_client_version()); assert!(initial.models.is_none()); seed_catalog(&runtime, &version("0.200.0")).await; @@ -2498,10 +2497,7 @@ mod tests { let rebound = read_codex_management_catalog(&runtime, TEST_PROVIDER_ID, TEST_KEY_ID) .await .unwrap(); - assert_eq!( - rebound.client_version, - aether_ai_formats::codex_client_version() - ); + assert_eq!(rebound.client_version, codex_client_version()); assert!(rebound.models.is_none()); } @@ -2557,10 +2553,7 @@ mod tests { format!("1.2.3-{}", "x".repeat(CODEX_CLIENT_VERSION_MAX_LEN)), ] { let normalized = normalize_codex_client_version(Some(&raw)); - assert_eq!( - normalized.as_str(), - aether_ai_formats::codex_client_version() - ); + assert_eq!(normalized.as_str(), codex_client_version()); assert!(normalized.used_fallback()); assert!(!catalog_lkg_key(&target(), normalized.as_str()).contains(&raw)); } @@ -3756,11 +3749,7 @@ mod tests { .await .expect("seed legacy cache"); - let load = load_one( - &runtime, - &version(&aether_ai_formats::codex_client_version()), - ) - .await; + let load = load_one(&runtime, &version(&codex_client_version())).await; assert!(load.snapshot(TEST_PROVIDER_ID, TEST_KEY_ID).is_none()); assert_eq!(runtime.execution_count(), 1); }