[apps] Add thread_id param to optionally load thread config for apps feature check. (#11279)

- [x] Add thread_id param to optionally load thread config for apps
feature check
This commit is contained in:
Matthew Zeng
2026-02-09 23:10:26 -08:00
committed by GitHub
parent 503186b31f
commit 005e040f97
11 changed files with 186 additions and 1 deletions

View File

@@ -23,6 +23,8 @@ use codex_app_server_protocol::JSONRPCError;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
use codex_core::auth::AuthCredentialsStoreMode;
use pretty_assertions::assert_eq;
use rmcp::handler::server::ServerHandler;
@@ -55,6 +57,7 @@ async fn list_apps_returns_empty_when_connectors_disabled() -> Result<()> {
.send_apps_list_request(AppsListParams {
limit: Some(50),
cursor: None,
thread_id: None,
force_refetch: false,
})
.await?;
@@ -72,6 +75,103 @@ async fn list_apps_returns_empty_when_connectors_disabled() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn list_apps_uses_thread_feature_flag_when_thread_id_is_provided() -> Result<()> {
let connectors = vec![AppInfo {
id: "beta".to_string(),
name: "Beta".to_string(),
description: Some("Beta connector".to_string()),
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
install_url: None,
is_accessible: false,
}];
let tools = vec![connector_tool("beta", "Beta App")?];
let (server_url, server_handle) =
start_apps_server_with_delays(connectors, tools, Duration::ZERO, Duration::ZERO).await?;
let codex_home = TempDir::new()?;
write_connectors_config(codex_home.path(), &server_url)?;
write_chatgpt_auth(
codex_home.path(),
ChatGptAuthFixture::new("chatgpt-token")
.account_id("account-123")
.chatgpt_user_id("user-123")
.chatgpt_account_id("account-123"),
AuthCredentialsStoreMode::File,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let start_request = mcp
.send_thread_start_request(ThreadStartParams::default())
.await?;
let start_response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(start_request)),
)
.await??;
let ThreadStartResponse { thread, .. } = to_response(start_response)?;
std::fs::write(
codex_home.path().join("config.toml"),
format!(
r#"
chatgpt_base_url = "{server_url}"
[features]
connectors = false
"#
),
)?;
let global_request = mcp
.send_apps_list_request(AppsListParams {
limit: None,
cursor: None,
thread_id: None,
force_refetch: false,
})
.await?;
let global_response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(global_request)),
)
.await??;
let AppsListResponse {
data: global_data,
next_cursor: global_next_cursor,
} = to_response(global_response)?;
assert!(global_data.is_empty());
assert!(global_next_cursor.is_none());
let thread_request = mcp
.send_apps_list_request(AppsListParams {
limit: None,
cursor: None,
thread_id: Some(thread.id),
force_refetch: false,
})
.await?;
let thread_response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(thread_request)),
)
.await??;
let AppsListResponse {
data: thread_data,
next_cursor: thread_next_cursor,
} = to_response(thread_response)?;
assert!(thread_data.iter().any(|app| app.id == "beta"));
assert!(thread_next_cursor.is_none());
server_handle.abort();
let _ = server_handle.await;
Ok(())
}
#[tokio::test]
async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<()> {
let connectors = vec![
@@ -124,6 +224,7 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<(
.send_apps_list_request(AppsListParams {
limit: None,
cursor: None,
thread_id: None,
force_refetch: false,
})
.await?;
@@ -238,6 +339,7 @@ async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
.send_apps_list_request(AppsListParams {
limit: None,
cursor: None,
thread_id: None,
force_refetch: false,
})
.await?;
@@ -360,6 +462,7 @@ async fn list_apps_paginates_results() -> Result<()> {
.send_apps_list_request(AppsListParams {
limit: Some(1),
cursor: None,
thread_id: None,
force_refetch: false,
})
.await?;
@@ -398,6 +501,7 @@ async fn list_apps_paginates_results() -> Result<()> {
.send_apps_list_request(AppsListParams {
limit: Some(1),
cursor: Some(next_cursor),
thread_id: None,
force_refetch: false,
})
.await?;
@@ -463,6 +567,7 @@ async fn list_apps_force_refetch_preserves_previous_cache_on_failure() -> Result
.send_apps_list_request(AppsListParams {
limit: None,
cursor: None,
thread_id: None,
force_refetch: false,
})
.await?;
@@ -492,6 +597,7 @@ async fn list_apps_force_refetch_preserves_previous_cache_on_failure() -> Result
.send_apps_list_request(AppsListParams {
limit: None,
cursor: None,
thread_id: None,
force_refetch: true,
})
.await?;
@@ -506,6 +612,7 @@ async fn list_apps_force_refetch_preserves_previous_cache_on_failure() -> Result
.send_apps_list_request(AppsListParams {
limit: None,
cursor: None,
thread_id: None,
force_refetch: false,
})
.await?;