feat(acp): add session/set_mode handler (#7801)

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Adrian Cole
2026-03-13 17:30:36 +08:00
committed by GitHub
parent bc7e063244
commit 1b05c92912
18 changed files with 815 additions and 372 deletions
+3 -1
View File
@@ -2,4 +2,6 @@ pub mod mcp;
pub mod session;
pub use mcp::{McpFixture, FAKE_CODE, TEST_IMAGE_B64};
pub use session::{ExpectedSessionId, TEST_MODEL, TEST_SESSION_ID};
pub use session::{
EnforceSessionId, ExpectedSessionId, IgnoreSessionId, TEST_MODEL, TEST_SESSION_ID,
};
+12 -13
View File
@@ -1,4 +1,5 @@
use crate::session::{ExpectedSessionId, SESSION_ID_HEADER};
use crate::session::SESSION_ID_HEADER;
use crate::ExpectedSessionId;
use rmcp::model::{
CallToolResult, ClientNotification, ClientRequest, Content, ErrorCode, Implementation,
InitializeResult, Meta, ProtocolVersion, ServerCapabilities, ServerInfo,
@@ -11,6 +12,7 @@ use rmcp::{
handler::server::router::tool::ToolRouter, tool, tool_handler, tool_router,
ErrorData as McpError, RoleServer, ServerHandler, Service,
};
use std::sync::Arc;
use tokio::task::JoinHandle;
pub const FAKE_CODE: &str = "test-uuid-12345-67890";
@@ -35,11 +37,11 @@ impl<R: ServiceRole> HasMeta for NotificationContext<R> {
struct ValidatingService<S> {
inner: S,
expected_session_id: ExpectedSessionId,
expected_session_id: Arc<dyn ExpectedSessionId>,
}
impl<S> ValidatingService<S> {
fn new(inner: S, expected_session_id: ExpectedSessionId) -> Self {
fn new(inner: S, expected_session_id: Arc<dyn ExpectedSessionId>) -> Self {
Self {
inner,
expected_session_id,
@@ -144,16 +146,13 @@ type McpServiceFactory =
Box<dyn Fn() -> Result<Box<dyn DynService<RoleServer>>, std::io::Error> + Send + Sync>;
impl McpFixture {
pub async fn new(expected_session_id: Option<ExpectedSessionId>) -> Self {
let service_factory: McpServiceFactory = match expected_session_id {
Some(expected_session_id) => Box::new(move || {
Ok(
ValidatingService::new(McpFixtureServer::new(), expected_session_id.clone())
.into_dyn(),
)
}),
None => Box::new(|| Ok(McpFixtureServer::new().into_dyn())),
};
pub async fn new(expected_session_id: Arc<dyn ExpectedSessionId>) -> Self {
let service_factory: McpServiceFactory = Box::new(move || {
Ok(
ValidatingService::new(McpFixtureServer::new(), expected_session_id.clone())
.into_dyn(),
)
});
let service = StreamableHttpService::new(
service_factory,
+23 -6
View File
@@ -6,13 +6,19 @@ pub const TEST_MODEL: &str = "gpt-5-nano";
const NOT_YET_SET: &str = "session-id-not-yet-set";
pub(crate) const SESSION_ID_HEADER: &str = "agent-session-id";
pub trait ExpectedSessionId: Send + Sync {
fn set(&self, id: &str);
fn validate(&self, actual: Option<&str>) -> Result<(), String>;
fn assert_matches(&self, actual: &str);
}
#[derive(Clone)]
pub struct ExpectedSessionId {
pub struct EnforceSessionId {
value: Arc<Mutex<String>>,
errors: Arc<Mutex<Vec<String>>>,
}
impl Default for ExpectedSessionId {
impl Default for EnforceSessionId {
fn default() -> Self {
Self {
value: Arc::new(Mutex::new(NOT_YET_SET.to_string())),
@@ -21,12 +27,12 @@ impl Default for ExpectedSessionId {
}
}
impl ExpectedSessionId {
pub fn set(&self, id: impl Into<String>) {
impl ExpectedSessionId for EnforceSessionId {
fn set(&self, id: &str) {
*self.value.lock().unwrap() = id.into();
}
pub fn validate(&self, actual: Option<&str>) -> Result<(), String> {
fn validate(&self, actual: Option<&str>) -> Result<(), String> {
let expected = self.value.lock().unwrap();
let err = match actual {
Some(act) if act == *expected => None,
@@ -44,7 +50,7 @@ impl ExpectedSessionId {
}
}
pub fn assert_matches(&self, actual: &str) {
fn assert_matches(&self, actual: &str) {
let result = self.validate(Some(actual));
assert!(result.is_ok(), "{}", result.unwrap_err());
let errors = self.errors.lock().unwrap();
@@ -55,3 +61,14 @@ impl ExpectedSessionId {
);
}
}
#[derive(Clone)]
pub struct IgnoreSessionId;
impl ExpectedSessionId for IgnoreSessionId {
fn set(&self, _id: &str) {}
fn validate(&self, _actual: Option<&str>) -> Result<(), String> {
Ok(())
}
fn assert_matches(&self, _actual: &str) {}
}