mirror of
https://github.com/openai/codex.git
synced 2026-05-23 20:44:50 +00:00
## Summary Startup tool construction currently depends on connector directory metadata for `tool_suggest` discoverables. On a cold directory cache, that can put slow connector-directory requests on the blocking path even though the tools array only needs directory data for install suggestions, not for the live connector MCP tools themselves. This PR keeps the discoverables path off that cold network fetch: - read connector directory metadata from cache only when building discoverable tools - persist connector directory metadata to `~/.codex/cache/codex_app_directory/<hash>.json` and use it to hydrate the in-memory cache on later runs before the normal refresh path updates it - use connector-directory-specific cache naming to distinguish this metadata cache from the separate Codex Apps tools-spec cache This reduces first-turn startup work without changing how live connector MCP tools are sourced. Longer term, directory-backed install suggestions should move to a search-based flow so they no longer need to be inlined into the tools prompt at all. ## Testing - `cargo test -p codex-connectors` - `cargo test -p codex-chatgpt` - `cargo test -p codex-core request_plugin_install_is_available_without_search_tool_after_discovery_attempts` - `cargo test -p codex-core tool_suggest_uses_connector_id_fallback_when_directory_cache_is_empty`
113 lines
3.4 KiB
Rust
113 lines
3.4 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use codex_app_server_protocol::AppInfo;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use sha1::Digest;
|
|
use sha1::Sha1;
|
|
use tracing::warn;
|
|
|
|
use crate::ConnectorDirectoryCacheKey;
|
|
|
|
pub(crate) const CONNECTOR_DIRECTORY_DISK_CACHE_SCHEMA_VERSION: u8 = 1;
|
|
const CONNECTOR_DIRECTORY_DISK_CACHE_DIR: &str = "cache/codex_app_directory";
|
|
|
|
#[derive(Clone)]
|
|
pub struct ConnectorDirectoryCacheContext {
|
|
pub(crate) codex_home: PathBuf,
|
|
pub(crate) cache_key: ConnectorDirectoryCacheKey,
|
|
}
|
|
|
|
impl ConnectorDirectoryCacheContext {
|
|
pub fn new(codex_home: PathBuf, cache_key: ConnectorDirectoryCacheKey) -> Self {
|
|
Self {
|
|
codex_home,
|
|
cache_key,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn cache_path(&self) -> PathBuf {
|
|
let cache_key_json = serde_json::to_string(&self.cache_key).unwrap_or_default();
|
|
let cache_key_hash = sha1_hex(&cache_key_json);
|
|
self.codex_home
|
|
.join(CONNECTOR_DIRECTORY_DISK_CACHE_DIR)
|
|
.join(format!("{cache_key_hash}.json"))
|
|
}
|
|
}
|
|
|
|
pub(crate) enum CachedConnectorDirectoryDiskLoad {
|
|
Hit { connectors: Vec<AppInfo> },
|
|
Missing,
|
|
Invalid,
|
|
}
|
|
|
|
pub(crate) fn load_cached_directory_connectors_from_disk(
|
|
cache_context: &ConnectorDirectoryCacheContext,
|
|
) -> CachedConnectorDirectoryDiskLoad {
|
|
let cache_path = cache_context.cache_path();
|
|
let bytes = match std::fs::read(&cache_path) {
|
|
Ok(bytes) => bytes,
|
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
|
return CachedConnectorDirectoryDiskLoad::Missing;
|
|
}
|
|
Err(err) => {
|
|
warn!(
|
|
cache_path = %cache_path.display(),
|
|
"failed to read connector directory disk cache: {err}"
|
|
);
|
|
return CachedConnectorDirectoryDiskLoad::Invalid;
|
|
}
|
|
};
|
|
let cache: ConnectorDirectoryDiskCache = match serde_json::from_slice(&bytes) {
|
|
Ok(cache) => cache,
|
|
Err(err) => {
|
|
warn!(
|
|
cache_path = %cache_path.display(),
|
|
"failed to parse connector directory disk cache: {err}"
|
|
);
|
|
let _ = std::fs::remove_file(cache_path);
|
|
return CachedConnectorDirectoryDiskLoad::Invalid;
|
|
}
|
|
};
|
|
if cache.schema_version != CONNECTOR_DIRECTORY_DISK_CACHE_SCHEMA_VERSION {
|
|
let _ = std::fs::remove_file(cache_path);
|
|
return CachedConnectorDirectoryDiskLoad::Invalid;
|
|
}
|
|
|
|
CachedConnectorDirectoryDiskLoad::Hit {
|
|
connectors: cache.connectors,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn write_cached_directory_connectors_to_disk(
|
|
cache_context: &ConnectorDirectoryCacheContext,
|
|
connectors: &[AppInfo],
|
|
) {
|
|
let cache_path = cache_context.cache_path();
|
|
if let Some(parent) = cache_path.parent()
|
|
&& std::fs::create_dir_all(parent).is_err()
|
|
{
|
|
return;
|
|
}
|
|
let Ok(bytes) = serde_json::to_vec_pretty(&ConnectorDirectoryDiskCache {
|
|
schema_version: CONNECTOR_DIRECTORY_DISK_CACHE_SCHEMA_VERSION,
|
|
connectors: connectors.to_vec(),
|
|
}) else {
|
|
return;
|
|
};
|
|
let _ = std::fs::write(cache_path, bytes);
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
struct ConnectorDirectoryDiskCache {
|
|
schema_version: u8,
|
|
connectors: Vec<AppInfo>,
|
|
}
|
|
|
|
fn sha1_hex(value: &str) -> String {
|
|
let mut hasher = Sha1::new();
|
|
hasher.update(value.as_bytes());
|
|
let sha1 = hasher.finalize();
|
|
format!("{sha1:x}")
|
|
}
|