Skip to content
Prev Previous commit
Move connector runtime into connectors crate
  • Loading branch information
mzeng-openai committed Jul 10, 2026
commit 4f6fb1bbf118525ec4092df7b679416715961837
5 changes: 5 additions & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

141 changes: 71 additions & 70 deletions codex-rs/codex-mcp/src/connection_manager_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,23 @@ fn create_codex_apps_tools_cache_context(
) -> CodexAppsToolsCacheContext {
CodexAppsToolsCache::default().context(
codex_home,
CodexAppsToolsCacheKey {
account_id: account_id.map(ToOwned::to_owned),
chatgpt_user_id: chatgpt_user_id.map(ToOwned::to_owned),
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(
account_id.map(ToOwned::to_owned),
chatgpt_user_id.map(ToOwned::to_owned),
),
)
}

fn store_current_tools(cache_context: &CodexAppsToolsCacheContext, tools: Vec<ToolInfo>) {
cache_context
.publish_if_newest_accepted(
cache_context.begin_fetch(CodexAppsToolsFetchSource::HardRefresh),
&create_test_server_info("Codex Apps"),
tools,
)
.expect("publish cached tools");
}

fn create_test_server_info(title: &str) -> McpServerInfo {
McpServerInfo {
name: "codex-apps".to_string(),
Expand Down Expand Up @@ -200,7 +209,7 @@ fn create_test_manager_with_failed_apps_startup(
Some("reconnect-test-account"),
Some("reconnect-test-user"),
);
cache_context.store_current_tools_for_test(cached_tools);
store_current_tools(&cache_context, cached_tools);
let approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
let permission_profile = Constrained::allow_any(PermissionProfile::default());
let mut manager = McpConnectionManager::new_uninitialized(
Expand Down Expand Up @@ -812,10 +821,13 @@ async fn list_all_tools_uses_shared_codex_apps_cache_while_client_is_pending() {
Some("account-one"),
Some("user-one"),
);
cache_context.store_current_tools_for_test(vec![create_test_tool(
CODEX_APPS_MCP_SERVER_NAME,
"calendar_create_event",
)]);
store_current_tools(
&cache_context,
vec![create_test_tool(
CODEX_APPS_MCP_SERVER_NAME,
"calendar_create_event",
)],
);
let pending_client = futures::future::pending::<Result<ManagedClient, StartupOutcomeError>>()
.boxed()
.shared();
Expand Down Expand Up @@ -1116,7 +1128,7 @@ async fn list_all_tools_does_not_block_when_shared_codex_apps_cache_is_empty() {
Some("account-one"),
Some("user-one"),
);
cache_context.store_current_tools_for_test(Vec::new());
store_current_tools(&cache_context, Vec::new());
let pending_client = futures::future::pending::<Result<ManagedClient, StartupOutcomeError>>()
.boxed()
.shared();
Expand Down Expand Up @@ -1157,10 +1169,13 @@ async fn list_all_tools_uses_shared_codex_apps_cache_when_client_startup_fails()
Some("account-one"),
Some("user-one"),
);
cache_context.store_current_tools_for_test(vec![create_test_tool(
CODEX_APPS_MCP_SERVER_NAME,
"calendar_create_event",
)]);
store_current_tools(
&cache_context,
vec![create_test_tool(
CODEX_APPS_MCP_SERVER_NAME,
"calendar_create_event",
)],
);
let server_info = create_test_server_info("Codex Apps");
let failed_client = futures::future::ready::<Result<ManagedClient, StartupOutcomeError>>(Err(
StartupOutcomeError::Failed {
Expand Down Expand Up @@ -1219,11 +1234,10 @@ async fn context_discard_while_checking_failed_startup_does_not_reconnect() {
let cache = CodexAppsToolsCache::default();
let context = cache.context(
codex_home.path().to_path_buf(),
CodexAppsToolsCacheKey {
account_id: Some("account-one".to_string()),
chatgpt_user_id: Some("user-one".to_string()),
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(
Some("account-one".to_string()),
Some("user-one".to_string()),
),
);
let (failure_started_tx, failure_started_rx) = tokio::sync::oneshot::channel();
let release_failure = Arc::new(tokio::sync::Notify::new());
Expand Down Expand Up @@ -1272,11 +1286,10 @@ async fn context_discard_while_checking_failed_startup_does_not_reconnect() {
.expect("failed startup check should begin");
let _new_context = cache.context(
codex_home.path().to_path_buf(),
CodexAppsToolsCacheKey {
account_id: Some("account-two".to_string()),
chatgpt_user_id: Some("user-two".to_string()),
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(
Some("account-two".to_string()),
Some("user-two".to_string()),
),
);
release_failure.notify_one();
reconnect_task.await.expect("reconnect check should finish");
Expand Down Expand Up @@ -1566,11 +1579,10 @@ async fn discarded_codex_apps_context_hides_tools_and_rejects_calls() {
let cache = CodexAppsToolsCache::default();
let context = cache.context(
codex_home.path().to_path_buf(),
CodexAppsToolsCacheKey {
account_id: Some("account-one".to_string()),
chatgpt_user_id: Some("user-one".to_string()),
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(
Some("account-one".to_string()),
Some("user-one".to_string()),
),
);
let mut client = create_ready_async_managed_client(vec![create_test_tool(
CODEX_APPS_MCP_SERVER_NAME,
Expand All @@ -1594,11 +1606,10 @@ async fn discarded_codex_apps_context_hides_tools_and_rejects_calls() {

let _new_context = cache.context(
codex_home.path().to_path_buf(),
CodexAppsToolsCacheKey {
account_id: Some("account-two".to_string()),
chatgpt_user_id: Some("user-two".to_string()),
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(
Some("account-two".to_string()),
Some("user-two".to_string()),
),
);

assert!(manager.list_all_tools().await.is_empty());
Expand All @@ -1623,11 +1634,10 @@ async fn context_discard_during_startup_hides_codex_apps_tools() {
let cache = CodexAppsToolsCache::default();
let context = cache.context(
codex_home.path().to_path_buf(),
CodexAppsToolsCacheKey {
account_id: Some("account-one".to_string()),
chatgpt_user_id: Some("user-one".to_string()),
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(
Some("account-one".to_string()),
Some("user-one".to_string()),
),
);
let (client, startup_started, release_startup) = create_blocked_codex_apps_client(
vec![create_test_tool(CODEX_APPS_MCP_SERVER_NAME, "old_tool")],
Expand Down Expand Up @@ -1656,11 +1666,10 @@ async fn context_discard_during_startup_hides_codex_apps_tools() {
.expect("tool listing should await startup");
let _new_context = cache.context(
codex_home.path().to_path_buf(),
CodexAppsToolsCacheKey {
account_id: Some("account-two".to_string()),
chatgpt_user_id: Some("user-two".to_string()),
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(
Some("account-two".to_string()),
Some("user-two".to_string()),
),
);
release_startup.notify_one();

Expand All @@ -1677,11 +1686,10 @@ async fn context_discard_during_startup_is_not_reported_ready() {
let cache = CodexAppsToolsCache::default();
let context = cache.context(
codex_home.path().to_path_buf(),
CodexAppsToolsCacheKey {
account_id: Some("account-one".to_string()),
chatgpt_user_id: Some("user-one".to_string()),
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(
Some("account-one".to_string()),
Some("user-one".to_string()),
),
);
let (client, startup_started, release_startup) =
create_blocked_codex_apps_client(Vec::new(), context, ToolFilter::default()).await;
Expand Down Expand Up @@ -1710,11 +1718,10 @@ async fn context_discard_during_startup_is_not_reported_ready() {
.expect("readiness check should await startup");
let _new_context = cache.context(
codex_home.path().to_path_buf(),
CodexAppsToolsCacheKey {
account_id: Some("account-two".to_string()),
chatgpt_user_id: Some("user-two".to_string()),
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(
Some("account-two".to_string()),
Some("user-two".to_string()),
),
);
release_startup.notify_one();

Expand All @@ -1732,11 +1739,10 @@ async fn context_discard_during_startup_rejects_codex_apps_calls() {
let cache = CodexAppsToolsCache::default();
let context = cache.context(
codex_home.path().to_path_buf(),
CodexAppsToolsCacheKey {
account_id: Some("account-one".to_string()),
chatgpt_user_id: Some("user-one".to_string()),
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(
Some("account-one".to_string()),
Some("user-one".to_string()),
),
);
let (client, startup_started, release_startup) = create_blocked_codex_apps_client(
vec![create_test_tool(CODEX_APPS_MCP_SERVER_NAME, "old_tool")],
Expand Down Expand Up @@ -1777,11 +1783,10 @@ async fn context_discard_during_startup_rejects_codex_apps_calls() {
.expect("tool call should await startup");
let _new_context = cache.context(
codex_home.path().to_path_buf(),
CodexAppsToolsCacheKey {
account_id: Some("account-two".to_string()),
chatgpt_user_id: Some("user-two".to_string()),
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(
Some("account-two".to_string()),
Some("user-two".to_string()),
),
);
release_startup.notify_one();

Expand Down Expand Up @@ -1934,11 +1939,7 @@ async fn no_local_runtime_fails_local_stdio_but_keeps_local_http_server() {
),
codex_home.path().to_path_buf(),
CodexAppsToolsCache::default(),
CodexAppsToolsCacheKey {
account_id: None,
chatgpt_user_id: None,
is_workspace_account: false,
},
CodexAppsToolsCacheKey::personal(/*account_id*/ None, /*chatgpt_user_id*/ None),
/*prefix_mcp_tool_names*/ true,
ElicitationCapability::default(),
/*supports_openai_form_elicitation*/ false,
Expand Down
52 changes: 52 additions & 0 deletions codex-rs/codex-mcp/src/connector_runtime/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//! MCP-facing aliases for the connector-owned runtime.

use codex_login::CodexAuth;
use codex_protocol::mcp::McpServerInfo;

use crate::tools::ToolInfo;

impl codex_connectors::ConnectorRuntimePayload for ToolInfo {
const TOOLS_CACHE_DIR: &'static str = "cache/codex_apps_tools";
const TOOLS_CACHE_SCHEMA_VERSION: u8 = 4;
const SERVER_INFO_CACHE_DIR: &'static str = "cache/codex_apps_server_info";
const SERVER_INFO_CACHE_SCHEMA_VERSION: u8 = 1;
}

pub type ConnectorRuntimeContextKey = codex_connectors::ConnectorRuntimeContextKey;
pub type ConnectorRuntimeManager = codex_connectors::ConnectorRuntimeManager<ToolInfo>;
pub type ConnectorRuntimeSnapshot = codex_connectors::ConnectorRuntimeSnapshot<ToolInfo>;

/// Compatibility alias for existing cache call sites.
pub type CodexAppsToolsCacheKey = ConnectorRuntimeContextKey;

/// Compatibility alias for existing cache call sites.
pub type CodexAppsToolsCache = ConnectorRuntimeManager;

pub(crate) type CodexAppsToolsCacheContext = codex_connectors::ConnectorRuntimeContext<ToolInfo>;
pub(crate) use codex_connectors::ConnectorRuntimeFetchSource as CodexAppsToolsFetchSource;

/// Builds the CodexAuth-backed connector runtime context key.
pub fn connector_runtime_context_key(auth: Option<&CodexAuth>) -> ConnectorRuntimeContextKey {
let account_id = auth.and_then(CodexAuth::get_account_id);
let chatgpt_user_id = auth.and_then(CodexAuth::get_chatgpt_user_id);
if auth.is_some_and(CodexAuth::is_workspace_account) {
ConnectorRuntimeContextKey::workspace(account_id, chatgpt_user_id)
} else {
ConnectorRuntimeContextKey::personal(account_id, chatgpt_user_id)
}
}

/// Compatibility helper for existing cache call sites.
pub fn codex_apps_tools_cache_key(auth: Option<&CodexAuth>) -> CodexAppsToolsCacheKey {
connector_runtime_context_key(auth)
}

pub(crate) fn load_startup_cached_codex_apps_server_info(
cache_context: &CodexAppsToolsCacheContext,
) -> Option<McpServerInfo> {
cache_context.cached_server_info()
}

#[cfg(test)]
#[path = "tests.rs"]
mod tests;
Loading
Loading