mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Allow injecting the Codex Apps tools cache (#33113)
## What changed - Accept a caller-provided `CodexAppsToolsCache` when constructing a `ThreadManager` and pass it through to the MCP manager. - Add `ConnectorRuntimeManager::new_without_cache()` for an in-memory runtime that neither loads nor persists connector state on disk. - Re-export `CodexAppsToolsCache` from `codex-core` and `codex-core-api` for embedders. ## Testing - Verify that a cache-disabled connector runtime ignores existing disk state and publishes live tools without creating cache files. GitOrigin-RevId: f52f4f27c425a8d84ad0d0987b30a6fc3c14d702
This commit is contained in:
@@ -117,12 +117,14 @@ impl<T> ConnectorRuntimeSnapshot<T> {
|
||||
/// remain independently available for clients that already hold their context.
|
||||
pub struct ConnectorRuntimeManager<T: ConnectorRuntimePayload> {
|
||||
entries: Arc<Mutex<HashMap<ConnectorRuntimeIdentity, Arc<ConnectorRuntimeEntry<T>>>>>,
|
||||
disk_cache: ConnectorRuntimeDiskCache,
|
||||
}
|
||||
|
||||
impl<T: ConnectorRuntimePayload> Clone for ConnectorRuntimeManager<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
entries: Arc::clone(&self.entries),
|
||||
disk_cache: self.disk_cache,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,11 +133,20 @@ impl<T: ConnectorRuntimePayload> Default for ConnectorRuntimeManager<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entries: Arc::new(Mutex::new(HashMap::new())),
|
||||
disk_cache: ConnectorRuntimeDiskCache::Enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ConnectorRuntimePayload> ConnectorRuntimeManager<T> {
|
||||
/// Constructs a process-local connector runtime that never reads or writes the disk cache.
|
||||
pub fn new_without_cache() -> Self {
|
||||
Self {
|
||||
entries: Arc::new(Mutex::new(HashMap::new())),
|
||||
disk_cache: ConnectorRuntimeDiskCache::Disabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_snapshot(
|
||||
&self,
|
||||
codex_home: PathBuf,
|
||||
@@ -153,7 +164,7 @@ impl<T: ConnectorRuntimePayload> ConnectorRuntimeManager<T> {
|
||||
let mut entries = lock_unpoisoned(&self.entries);
|
||||
let entry = entries
|
||||
.entry(identity.clone())
|
||||
.or_insert_with(|| Arc::new(ConnectorRuntimeEntry::new(identity)))
|
||||
.or_insert_with(|| Arc::new(ConnectorRuntimeEntry::new(identity, self.disk_cache)))
|
||||
.clone();
|
||||
ConnectorRuntimeContext { entry }
|
||||
}
|
||||
@@ -193,7 +204,10 @@ impl<T: ConnectorRuntimePayload> ConnectorRuntimeContext<T> {
|
||||
}
|
||||
|
||||
pub fn cached_server_info(&self) -> Option<McpServerInfo> {
|
||||
load_cached_codex_apps_server_info(self)
|
||||
match self.entry.disk_cache {
|
||||
ConnectorRuntimeDiskCache::Enabled => load_cached_codex_apps_server_info(self),
|
||||
ConnectorRuntimeDiskCache::Disabled => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn tools_cache_path(&self) -> PathBuf {
|
||||
@@ -215,12 +229,20 @@ impl<T: ConnectorRuntimePayload> ConnectorRuntimeContext<T> {
|
||||
server_info: &McpServerInfo,
|
||||
tools: Vec<T>,
|
||||
) -> Arc<ConnectorRuntimeSnapshot<T>> {
|
||||
self.publish_runtime_if_newest_accepted_with(
|
||||
ticket,
|
||||
server_info,
|
||||
tools,
|
||||
persist_codex_apps_cache,
|
||||
)
|
||||
match self.entry.disk_cache {
|
||||
ConnectorRuntimeDiskCache::Enabled => self.publish_runtime_if_newest_accepted_with(
|
||||
ticket,
|
||||
server_info,
|
||||
tools,
|
||||
persist_codex_apps_cache,
|
||||
),
|
||||
ConnectorRuntimeDiskCache::Disabled => self.publish_runtime_if_newest_accepted_with(
|
||||
ticket,
|
||||
server_info,
|
||||
tools,
|
||||
|_, _, _| {},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn publish_runtime_if_newest_accepted_with(
|
||||
@@ -300,16 +322,23 @@ pub struct ConnectorRuntimeFetchTicket {
|
||||
/// All live state owned by one connector identity.
|
||||
struct ConnectorRuntimeEntry<T: ConnectorRuntimePayload> {
|
||||
identity: ConnectorRuntimeIdentity,
|
||||
disk_cache: ConnectorRuntimeDiskCache,
|
||||
current_snapshot: ArcSwapOption<ConnectorRuntimeSnapshot<T>>,
|
||||
next_fetch_generation: AtomicU64,
|
||||
last_accepted_generation: Mutex<u64>,
|
||||
}
|
||||
|
||||
impl<T: ConnectorRuntimePayload> ConnectorRuntimeEntry<T> {
|
||||
fn new(identity: ConnectorRuntimeIdentity) -> Self {
|
||||
let current_snapshot = load_cached_connector_runtime_for_identity(&identity).map(Arc::new);
|
||||
fn new(identity: ConnectorRuntimeIdentity, disk_cache: ConnectorRuntimeDiskCache) -> Self {
|
||||
let current_snapshot = match disk_cache {
|
||||
ConnectorRuntimeDiskCache::Enabled => {
|
||||
load_cached_connector_runtime_for_identity(&identity).map(Arc::new)
|
||||
}
|
||||
ConnectorRuntimeDiskCache::Disabled => None,
|
||||
};
|
||||
Self {
|
||||
identity,
|
||||
disk_cache,
|
||||
current_snapshot: ArcSwapOption::from(current_snapshot),
|
||||
next_fetch_generation: AtomicU64::new(0),
|
||||
last_accepted_generation: Mutex::new(0),
|
||||
@@ -317,6 +346,12 @@ impl<T: ConnectorRuntimePayload> ConnectorRuntimeEntry<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum ConnectorRuntimeDiskCache {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
/// Everything that decides whether two connector runtime clients can share a snapshot.
|
||||
///
|
||||
/// The auth key says whose runtime catalog we are reading. `codex_home` keeps
|
||||
|
||||
@@ -413,6 +413,54 @@ fn codex_apps_tools_cache_keeps_live_publish_when_disk_persistence_fails() {
|
||||
assert_eq!(cache_context.current_tools(), Some(tools));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connector_runtime_without_cache_ignores_disk_state() {
|
||||
let codex_home = tempdir().expect("tempdir");
|
||||
let writer = create_codex_apps_tools_cache_context(
|
||||
codex_home.path().to_path_buf(),
|
||||
Some("account-one"),
|
||||
Some("user-one"),
|
||||
);
|
||||
let tools = vec![create_test_tool(CODEX_APPS_MCP_SERVER_NAME, "cached")];
|
||||
let server_info = create_test_server_info("Codex Apps");
|
||||
write_cached_codex_apps_tools_for_test(&writer, &server_info, &tools);
|
||||
let context = ConnectorRuntimeManager::<TestTool>::new_without_cache().context(
|
||||
codex_home.path().to_path_buf(),
|
||||
ConnectorRuntimeContextKey {
|
||||
account_id: Some("account-one".to_string()),
|
||||
chatgpt_user_id: Some("user-one".to_string()),
|
||||
is_workspace_account: false,
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(context.current_tools(), None);
|
||||
assert_eq!(context.cached_server_info(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connector_runtime_without_cache_publishes_without_writing() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let codex_home = temp_dir.path().join("codex-home");
|
||||
let context = ConnectorRuntimeManager::<TestTool>::new_without_cache().context(
|
||||
codex_home.clone(),
|
||||
ConnectorRuntimeContextKey {
|
||||
account_id: Some("account-one".to_string()),
|
||||
chatgpt_user_id: Some("user-one".to_string()),
|
||||
is_workspace_account: false,
|
||||
},
|
||||
);
|
||||
let tools = vec![create_test_tool(CODEX_APPS_MCP_SERVER_NAME, "live")];
|
||||
let published_tools = context.publish_if_newest_accepted(
|
||||
context.begin_fetch(ConnectorRuntimeFetchSource::HardRefresh),
|
||||
&create_test_server_info("Codex Apps"),
|
||||
tools.clone(),
|
||||
);
|
||||
|
||||
assert_eq!(published_tools, tools);
|
||||
assert_eq!(context.current_tools(), Some(tools));
|
||||
assert!(!codex_home.exists());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn codex_apps_tools_cache_scopes_non_utf8_home_disk_paths() {
|
||||
|
||||
Reference in New Issue
Block a user