mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-04 08:27:46 +08:00
fix(codex): confine codex profile api to ai_serving root seams
This commit is contained in:
@@ -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,
|
/// Codex client identity accessors re-exported for out-of-crate probe binaries,
|
||||||
/// which must reach the runtime profile through this seam.
|
/// which must reach the runtime profile through this seam.
|
||||||
pub use aether_ai_formats::{codex_client_originator, codex_client_user_agent};
|
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) use aether_ai_formats::{CODEX_RESPONSES_LITE_HEADER, UPSTREAM_IS_STREAM_KEY};
|
||||||
|
|
||||||
pub(crate) fn parse_direct_request_body(
|
pub(crate) fn parse_direct_request_body(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use semver::Version;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
use crate::ai_serving::api::{codex_client_version, set_codex_cli_version};
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
const CLI_RELEASE_ENDPOINT: &str = "https://registry.npmjs.org/@openai%2Fcodex/latest";
|
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::<CachedProfile>(&raw)
|
let cached = serde_json::from_str::<CachedProfile>(&raw)
|
||||||
.map_err(|_| ProfileRefreshError::InvalidMetadata)?;
|
.map_err(|_| ProfileRefreshError::InvalidMetadata)?;
|
||||||
if let Some(version) =
|
if let Some(version) = cached_version_to_restore(&cached, &codex_client_version())? {
|
||||||
cached_version_to_restore(&cached, &aether_ai_formats::codex_client_version())?
|
set_codex_cli_version(&version).map_err(|_| ProfileRefreshError::InvalidMetadata)?;
|
||||||
{
|
|
||||||
aether_ai_formats::set_codex_cli_version(&version)
|
|
||||||
.map_err(|_| ProfileRefreshError::InvalidMetadata)?;
|
|
||||||
info!(
|
info!(
|
||||||
event_name = "codex_client_profile_restored",
|
event_name = "codex_client_profile_restored",
|
||||||
version = %version,
|
version = %version,
|
||||||
@@ -216,8 +214,7 @@ where
|
|||||||
Fut: Future<Output = Result<String, ProfileRefreshError>>,
|
Fut: Future<Output = Result<String, ProfileRefreshError>>,
|
||||||
{
|
{
|
||||||
if let Some(version) = fixed_version {
|
if let Some(version) = fixed_version {
|
||||||
aether_ai_formats::set_codex_cli_version(version)
|
set_codex_cli_version(version).map_err(|_| ProfileRefreshError::InvalidMetadata)?;
|
||||||
.map_err(|_| ProfileRefreshError::InvalidMetadata)?;
|
|
||||||
return Ok(version.to_owned());
|
return Ok(version.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,11 +227,11 @@ where
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if !refresh_is_enabled {
|
if !refresh_is_enabled {
|
||||||
return Ok(aether_ai_formats::codex_client_version());
|
return Ok(codex_client_version());
|
||||||
}
|
}
|
||||||
|
|
||||||
let version = fetch_latest().await?;
|
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)? {
|
if version_sequence(&version)? < version_sequence(¤t)? {
|
||||||
return Err(ProfileRefreshError::Rollback);
|
return Err(ProfileRefreshError::Rollback);
|
||||||
}
|
}
|
||||||
@@ -245,8 +242,7 @@ where
|
|||||||
};
|
};
|
||||||
let serialized =
|
let serialized =
|
||||||
serde_json::to_string(&cached).map_err(|_| ProfileRefreshError::InvalidMetadata)?;
|
serde_json::to_string(&cached).map_err(|_| ProfileRefreshError::InvalidMetadata)?;
|
||||||
aether_ai_formats::set_codex_cli_version(&version)
|
set_codex_cli_version(&version).map_err(|_| ProfileRefreshError::InvalidMetadata)?;
|
||||||
.map_err(|_| ProfileRefreshError::InvalidMetadata)?;
|
|
||||||
if let Err(error) = runtime
|
if let Err(error) = runtime
|
||||||
.kv_set(PROFILE_CACHE_KEY, serialized, Some(PROFILE_CACHE_TTL))
|
.kv_set(PROFILE_CACHE_KEY, serialized, Some(PROFILE_CACHE_TTL))
|
||||||
.await
|
.await
|
||||||
@@ -321,21 +317,25 @@ mod tests {
|
|||||||
cached_version_to_restore, fixed_version_from, parse_cli_release, refresh_enabled_from,
|
cached_version_to_restore, fixed_version_from, parse_cli_release, refresh_enabled_from,
|
||||||
refresh_once_with_fetch, CachedProfile, ProfileRefreshError, PROFILE_CACHE_KEY,
|
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<Mutex<()>> = OnceLock::new();
|
static PROFILE_TEST_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
||||||
|
|
||||||
struct ProfileRestore(aether_ai_formats::CodexClientProfile);
|
struct ProfileRestore(CodexClientProfile);
|
||||||
|
|
||||||
impl Drop for ProfileRestore {
|
impl Drop for ProfileRestore {
|
||||||
fn drop(&mut self) {
|
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) {
|
fn profile_restore_guard() -> (std::sync::MutexGuard<'static, ()>, ProfileRestore) {
|
||||||
let lock = PROFILE_TEST_LOCK.get_or_init(|| Mutex::new(()));
|
let lock = PROFILE_TEST_LOCK.get_or_init(|| Mutex::new(()));
|
||||||
let guard = lock.lock().expect("profile test lock");
|
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)
|
(guard, restore)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,21 +419,21 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(result, "0.200.1");
|
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]
|
#[tokio::test]
|
||||||
async fn refresh_failure_keeps_previous_profile() {
|
async fn refresh_failure_keeps_previous_profile() {
|
||||||
let (_lock, _restore) = profile_restore_guard();
|
let (_lock, _restore) = profile_restore_guard();
|
||||||
let runtime = RuntimeState::memory(MemoryRuntimeStateConfig::default());
|
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 {
|
let result = refresh_once_with_fetch(&runtime, None, true, || async {
|
||||||
Err(ProfileRefreshError::HttpStatus(503))
|
Err(ProfileRefreshError::HttpStatus(503))
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(matches!(result, Err(ProfileRefreshError::HttpStatus(503))));
|
assert!(matches!(result, Err(ProfileRefreshError::HttpStatus(503))));
|
||||||
assert_eq!(aether_ai_formats::codex_client_profile(), before);
|
assert_eq!(codex_client_profile(), before);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -450,19 +450,19 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(result, "0.220.0");
|
assert_eq!(result, "0.220.0");
|
||||||
assert!(!fetch_called.load(Ordering::SeqCst));
|
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]
|
#[tokio::test]
|
||||||
async fn rollback_is_rejected_without_replacing_profile() {
|
async fn rollback_is_rejected_without_replacing_profile() {
|
||||||
let (_lock, _restore) = profile_restore_guard();
|
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 runtime = RuntimeState::memory(MemoryRuntimeStateConfig::default());
|
||||||
let result =
|
let result =
|
||||||
refresh_once_with_fetch(&runtime, None, true, || async { Ok("0.219.9".to_string()) })
|
refresh_once_with_fetch(&runtime, None, true, || async { Ok("0.219.9".to_string()) })
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(matches!(result, Err(ProfileRefreshError::Rollback)));
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(|| {
|
let client_version = is_codex.then(|| {
|
||||||
codex_catalog
|
codex_catalog
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ use sha2::{Digest, Sha256};
|
|||||||
use tokio::sync::{Mutex, Semaphore};
|
use tokio::sync::{Mutex, Semaphore};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
|
use crate::ai_serving::api::codex_client_version;
|
||||||
|
|
||||||
const CODEX_CATALOG_SCHEMA_VERSION: u32 = 2;
|
const CODEX_CATALOG_SCHEMA_VERSION: u32 = 2;
|
||||||
const CODEX_CATALOG_CREDENTIAL_SCOPE_DOMAIN: &str = "aether-codex-catalog-credential-v2";
|
const CODEX_CATALOG_CREDENTIAL_SCOPE_DOMAIN: &str = "aether-codex-catalog-credential-v2";
|
||||||
const CODEX_CLIENT_VERSION_MAX_LEN: usize = 64;
|
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,
|
used_fallback: false,
|
||||||
},
|
},
|
||||||
None => NormalizedCodexClientVersion {
|
None => NormalizedCodexClientVersion {
|
||||||
value: aether_ai_formats::codex_client_version(),
|
value: codex_client_version(),
|
||||||
used_fallback: true,
|
used_fallback: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1521,7 +1523,7 @@ where
|
|||||||
.await?;
|
.await?;
|
||||||
let scope = target.credential_scope()?;
|
let scope = target.credential_scope()?;
|
||||||
let state = runtime.codex_catalog_runtime_state();
|
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) =
|
if let Some(recent) =
|
||||||
read_recent_codex_catalog_client_version(state, provider_id, key_id, scope).await
|
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)
|
let initial = read_codex_management_catalog(&runtime, TEST_PROVIDER_ID, TEST_KEY_ID)
|
||||||
.await
|
.await
|
||||||
.expect("management context");
|
.expect("management context");
|
||||||
assert_eq!(
|
assert_eq!(initial.client_version, codex_client_version());
|
||||||
initial.client_version,
|
|
||||||
aether_ai_formats::codex_client_version()
|
|
||||||
);
|
|
||||||
assert!(initial.models.is_none());
|
assert!(initial.models.is_none());
|
||||||
|
|
||||||
seed_catalog(&runtime, &version("0.200.0")).await;
|
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)
|
let rebound = read_codex_management_catalog(&runtime, TEST_PROVIDER_ID, TEST_KEY_ID)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(rebound.client_version, codex_client_version());
|
||||||
rebound.client_version,
|
|
||||||
aether_ai_formats::codex_client_version()
|
|
||||||
);
|
|
||||||
assert!(rebound.models.is_none());
|
assert!(rebound.models.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2557,10 +2553,7 @@ mod tests {
|
|||||||
format!("1.2.3-{}", "x".repeat(CODEX_CLIENT_VERSION_MAX_LEN)),
|
format!("1.2.3-{}", "x".repeat(CODEX_CLIENT_VERSION_MAX_LEN)),
|
||||||
] {
|
] {
|
||||||
let normalized = normalize_codex_client_version(Some(&raw));
|
let normalized = normalize_codex_client_version(Some(&raw));
|
||||||
assert_eq!(
|
assert_eq!(normalized.as_str(), codex_client_version());
|
||||||
normalized.as_str(),
|
|
||||||
aether_ai_formats::codex_client_version()
|
|
||||||
);
|
|
||||||
assert!(normalized.used_fallback());
|
assert!(normalized.used_fallback());
|
||||||
assert!(!catalog_lkg_key(&target(), normalized.as_str()).contains(&raw));
|
assert!(!catalog_lkg_key(&target(), normalized.as_str()).contains(&raw));
|
||||||
}
|
}
|
||||||
@@ -3756,11 +3749,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("seed legacy cache");
|
.expect("seed legacy cache");
|
||||||
|
|
||||||
let load = load_one(
|
let load = load_one(&runtime, &version(&codex_client_version())).await;
|
||||||
&runtime,
|
|
||||||
&version(&aether_ai_formats::codex_client_version()),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert!(load.snapshot(TEST_PROVIDER_ID, TEST_KEY_ID).is_none());
|
assert!(load.snapshot(TEST_PROVIDER_ID, TEST_KEY_ID).is_none());
|
||||||
assert_eq!(runtime.execution_count(), 1);
|
assert_eq!(runtime.execution_count(), 1);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user