feat: opt-out of events in the app-server (#11319)

Add `optOutNotificationMethods` in the app-server to opt-out events
based on exact method matching
This commit is contained in:
jif-oai
2026-02-10 18:04:52 +00:00
committed by GitHub
parent 48e415bdef
commit a364dd8b56
15 changed files with 284 additions and 7 deletions

View File

@@ -30,6 +30,7 @@ async fn mock_experimental_method_requires_experimental_api_capability() -> Resu
default_client_info(),
Some(InitializeCapabilities {
experimental_api: false,
opt_out_notification_methods: None,
}),
)
.await?;
@@ -61,6 +62,7 @@ async fn thread_start_mock_field_requires_experimental_api_capability() -> Resul
default_client_info(),
Some(InitializeCapabilities {
experimental_api: false,
opt_out_notification_methods: None,
}),
)
.await?;
@@ -97,6 +99,7 @@ async fn thread_start_without_dynamic_tools_allows_without_experimental_api_capa
default_client_info(),
Some(InitializeCapabilities {
experimental_api: false,
opt_out_notification_methods: None,
}),
)
.await?;

View File

@@ -3,8 +3,12 @@ use app_test_support::McpProcess;
use app_test_support::create_mock_responses_server_sequence_unchecked;
use app_test_support::to_response;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::InitializeCapabilities;
use codex_app_server_protocol::InitializeResponse;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
use pretty_assertions::assert_eq;
use std::path::Path;
use tempfile::TempDir;
@@ -108,6 +112,72 @@ async fn initialize_rejects_invalid_client_name() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn initialize_opt_out_notification_methods_filters_notifications() -> Result<()> {
let responses = Vec::new();
let server = create_mock_responses_server_sequence_unchecked(responses).await;
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path(), &server.uri(), "never")?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
let message = timeout(
DEFAULT_READ_TIMEOUT,
mcp.initialize_with_capabilities(
ClientInfo {
name: "codex_vscode".to_string(),
title: Some("Codex VS Code Extension".to_string()),
version: "0.1.0".to_string(),
},
Some(InitializeCapabilities {
experimental_api: true,
opt_out_notification_methods: Some(vec![
"thread/started".to_string(),
"codex/event/session_configured".to_string(),
]),
}),
),
)
.await??;
let JSONRPCMessage::Response(_) = message else {
anyhow::bail!("expected initialize response, got {message:?}");
};
let request_id = mcp
.send_thread_start_request(ThreadStartParams::default())
.await?;
let response = timeout(DEFAULT_READ_TIMEOUT, async {
loop {
let message = mcp.read_next_message().await?;
match message {
JSONRPCMessage::Response(response)
if response.id == RequestId::Integer(request_id) =>
{
return Ok(response);
}
JSONRPCMessage::Notification(notification)
if notification.method == "thread/started" =>
{
anyhow::bail!("thread/started should be filtered by optOutNotificationMethods");
}
_ => {}
}
}
})
.await??;
let _: ThreadStartResponse = to_response(response)?;
let thread_started = timeout(
std::time::Duration::from_millis(500),
mcp.read_stream_until_notification_message("thread/started"),
)
.await;
assert!(
thread_started.is_err(),
"thread/started should be filtered by optOutNotificationMethods"
);
Ok(())
}
// Helper to create a config.toml pointing at the mock model server.
fn create_config_toml(
codex_home: &Path,