mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
## What changed - Update the pinned `rmcp` dependency and Cargo/Bazel lockfiles, and migrate MCP handlers and fixtures to `ClientConfig` and `ServerConfig`. - Stop requiring responses for cancelled elicitation requests in cancellation tests while retaining checks for cleanup and continued connection usability. - Handle client disconnects during WebSocket writes in the TUI test server, use an asynchronous HTTP mock for loopback proxy testing, and improve remote executor test diagnostics and the Windows subprocess fixture. - Use `invalid_prompt` for the memory worker failure fixture. ## Testing Add a regression test covering transport EOF during both form elicitation and user verification. Keep the client alive while checking that pending response channels close, the timeout pause clears, and the service exits with `QuitReason::Closed`. GitOrigin-RevId: c0166b83fab3b59d84a5207c6630cb6adcf3b230
102 lines
4.2 KiB
Rust
102 lines
4.2 KiB
Rust
//! Exercises pending MRTR input cleanup over a real duplex transport.
|
|
|
|
use super::ElicitationClientService;
|
|
use crate::rmcp_client::ElicitationPauseState;
|
|
use crate::tool_input::call_tool;
|
|
use codex_protocol::mcp::OPENAI_ELICITATION_EXTENSION_ID;
|
|
use rmcp::RoleServer;
|
|
use rmcp::model::CallToolRequestParams;
|
|
use rmcp::model::ClientConfig;
|
|
use rmcp::model::ClientJsonRpcMessage;
|
|
use rmcp::model::CustomResult;
|
|
use rmcp::model::ServerJsonRpcMessage;
|
|
use rmcp::model::ServerResult;
|
|
use rmcp::service::ServiceError;
|
|
use rmcp::service::serve_directly;
|
|
use rmcp::transport::IntoTransport;
|
|
use rmcp::transport::Transport;
|
|
use serde_json::json;
|
|
use std::time::Duration;
|
|
use tokio::sync::mpsc;
|
|
use tokio::sync::oneshot;
|
|
use tokio::time::timeout;
|
|
|
|
#[tokio::test]
|
|
async fn connection_closure_releases_pending_inputs_and_timeout_pause() -> anyhow::Result<()> {
|
|
for mode in ["form", "openai/userVerification"] {
|
|
for close_transport in [false, true] {
|
|
let pause_state = ElicitationPauseState::new();
|
|
let mut paused = pause_state.subscribe();
|
|
let (route_tx, mut route_rx) = mpsc::unbounded_channel();
|
|
let mut info = ClientConfig::default();
|
|
info.capabilities.extensions = Some(
|
|
[(
|
|
OPENAI_ELICITATION_EXTENSION_ID.into(),
|
|
serde_json::Map::from_iter([
|
|
("userVerification".into(), json!({})),
|
|
("form".into(), json!({})),
|
|
]),
|
|
)]
|
|
.into_iter()
|
|
.collect(),
|
|
);
|
|
let service = ElicitationClientService::new(
|
|
info,
|
|
Box::new(move |_, _| {
|
|
let (tx, rx) = oneshot::channel();
|
|
route_tx.send(tx).unwrap();
|
|
Box::pin(async move { Ok(rx.await?) })
|
|
}),
|
|
pause_state,
|
|
);
|
|
let (client_transport, server_transport) =
|
|
tokio::io::duplex(/*max_buf_size*/ 4096);
|
|
let client = serve_directly(service, client_transport, /*peer_info*/ None);
|
|
let mut server = IntoTransport::<RoleServer, _, _>::into_transport(server_transport);
|
|
let call = call_tool(&client, CallToolRequestParams::new("test"));
|
|
tokio::pin!(call);
|
|
let request = tokio::select! {
|
|
result = &mut call => panic!("tool completed before input: {result:?}"),
|
|
request = timeout(Duration::from_secs(/*secs*/ 5), server.receive()) => request?,
|
|
};
|
|
let Some(ClientJsonRpcMessage::Request(request)) = request else {
|
|
anyhow::bail!("expected tool call");
|
|
};
|
|
let params = match mode {
|
|
"form" => json!({
|
|
"mode": mode, "message": "Confirm",
|
|
"requestedSchema": {"type": "object", "properties": {}},
|
|
}),
|
|
_ => json!({
|
|
"mode": mode, "title": "Verify", "description": "", "challenge": "AQID",
|
|
}),
|
|
};
|
|
server.send(ServerJsonRpcMessage::response(
|
|
ServerResult::CustomResult(CustomResult(json!({
|
|
"resultType": "input_required",
|
|
"inputRequests": {"input": {"method": "openai/elicitation/create", "params": params}},
|
|
}))),
|
|
request.id,
|
|
)).await?;
|
|
let reply = tokio::select! {
|
|
result = &mut call => panic!("tool completed before prompting: {result:?}"),
|
|
reply = timeout(Duration::from_secs(/*secs*/ 5), route_rx.recv()) => reply?.unwrap(),
|
|
};
|
|
assert!(*paused.borrow());
|
|
if close_transport {
|
|
drop(server);
|
|
} else {
|
|
client.cancellation_token().cancel();
|
|
}
|
|
let result = timeout(Duration::from_secs(/*secs*/ 5), call).await?;
|
|
assert!(
|
|
matches!(result, Err(ServiceError::TransportClosed)),
|
|
"{result:?}"
|
|
);
|
|
assert!(reply.is_closed());
|
|
assert!(!*paused.borrow_and_update());
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|