Scope Codex Apps cache by plugin connectors

This commit is contained in:
malsamiri
2026-07-07 15:41:48 -07:00
parent c11996e15d
commit e69dab677a
2 changed files with 52 additions and 3 deletions
+15 -3
View File
@@ -78,7 +78,7 @@ impl CodexAppsToolsCacheContext {
pub(crate) fn server_info_cache_path(&self) -> PathBuf {
self.entry
.identity
.cache_path_in(CODEX_APPS_SERVER_INFO_CACHE_DIR)
.auth_cache_path_in(CODEX_APPS_SERVER_INFO_CACHE_DIR)
}
pub(crate) fn current_tools(&self) -> Option<Vec<ToolInfo>> {
@@ -156,11 +156,12 @@ impl CodexAppsToolsCache {
let mut expected_connector_ids = expected_connector_ids.into_iter().collect::<Vec<_>>();
expected_connector_ids.sort_unstable();
expected_connector_ids.dedup();
let expected_connector_ids = Arc::new(expected_connector_ids);
let identity = CodexAppsToolsCacheIdentity {
codex_home,
auth_key,
expected_connector_ids: expected_connector_ids.clone(),
};
let expected_connector_ids = Arc::new(expected_connector_ids);
let mut entries = lock_unpoisoned(&self.entries);
let entry = entries
.entry(identity.clone())
@@ -214,12 +215,14 @@ impl CodexAppsToolsCacheEntry {
/// Everything that decides whether two Codex Apps clients can share tools.
///
/// The auth key says whose catalog we are reading. `codex_home` keeps the
/// The auth key says whose catalog we are reading. Expected connector IDs keep
/// plugin contexts from sharing a raw snapshot. `codex_home` keeps the
/// persisted cache under the right home directory.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct CodexAppsToolsCacheIdentity {
codex_home: PathBuf,
auth_key: CodexAppsToolsCacheKey,
expected_connector_ids: Vec<String>,
}
impl CodexAppsToolsCacheIdentity {
@@ -227,6 +230,15 @@ impl CodexAppsToolsCacheIdentity {
// `codex_home` is already the parent directory. Keep it out of the
// filename hash so non-UTF-8 Unix paths cannot collapse distinct auth
// keys onto the same disk cache file.
let identity_json = serde_json::to_string(&(&self.auth_key, &self.expected_connector_ids))
.unwrap_or_default();
let identity_hash = sha1_hex(&identity_json);
self.codex_home
.join(cache_dir)
.join(format!("{identity_hash}.json"))
}
fn auth_cache_path_in(&self, cache_dir: &str) -> PathBuf {
let identity_json = serde_json::to_string(&self.auth_key).unwrap_or_default();
let identity_hash = sha1_hex(&identity_json);
self.codex_home
@@ -405,6 +405,43 @@ fn codex_apps_tools_cache_publishes_newest_shared_snapshot() {
);
}
#[test]
fn codex_apps_tools_cache_scopes_plugin_connector_expectations() {
let codex_home = tempdir().expect("tempdir");
let cache = CodexAppsToolsCache::default();
let auth_key = CodexAppsToolsCacheKey {
account_id: Some("account-one".to_string()),
chatgpt_user_id: Some("user-one".to_string()),
is_workspace_account: false,
};
let connector_a = cache.context(
codex_home.path().to_path_buf(),
auth_key.clone(),
["connector_a".to_string()],
);
let connector_b = cache.context(
codex_home.path().to_path_buf(),
auth_key,
["connector_b".to_string()],
);
connector_a.store_current_tools_for_test(vec![create_test_tool(
CODEX_APPS_MCP_SERVER_NAME,
"connector_a_search",
)]);
assert!(connector_a.current_tools().is_some());
assert!(connector_b.current_tools().is_none());
assert_ne!(
connector_a.tools_cache_path(),
connector_b.tools_cache_path()
);
assert_eq!(
connector_a.server_info_cache_path(),
connector_b.server_info_cache_path()
);
}
#[test]
fn codex_apps_tools_cache_keeps_live_publish_when_disk_persistence_fails() {
let codex_home = tempdir().expect("tempdir");