ACP over MCP server impl (#35196)

Release Notes:

- N/A

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
Agus Zubiaga 2025-07-28 11:14:10 -03:00 committed by GitHub
parent b02ae771cd
commit c2fc70eef7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 899 additions and 137 deletions

View file

@ -330,23 +330,16 @@ impl Client {
method: &str,
params: impl Serialize,
) -> Result<T> {
self.request_impl(method, params, None).await
self.request_with(method, params, None, Some(REQUEST_TIMEOUT))
.await
}
pub async fn cancellable_request<T: DeserializeOwned>(
&self,
method: &str,
params: impl Serialize,
cancel_rx: oneshot::Receiver<()>,
) -> Result<T> {
self.request_impl(method, params, Some(cancel_rx)).await
}
pub async fn request_impl<T: DeserializeOwned>(
pub async fn request_with<T: DeserializeOwned>(
&self,
method: &str,
params: impl Serialize,
cancel_rx: Option<oneshot::Receiver<()>>,
timeout: Option<Duration>,
) -> Result<T> {
let id = self.next_id.fetch_add(1, SeqCst);
let request = serde_json::to_string(&Request {
@ -382,7 +375,13 @@ impl Client {
handle_response?;
send?;
let mut timeout = executor.timer(REQUEST_TIMEOUT).fuse();
let mut timeout_fut = pin!(
match timeout {
Some(timeout) => future::Either::Left(executor.timer(timeout)),
None => future::Either::Right(future::pending()),
}
.fuse()
);
let mut cancel_fut = pin!(
match cancel_rx {
Some(rx) => future::Either::Left(async {
@ -419,10 +418,10 @@ impl Client {
reason: None
})
).log_err();
anyhow::bail!("Request cancelled")
anyhow::bail!(RequestCanceled)
}
_ = timeout => {
log::error!("cancelled csp request task for {method:?} id {id} which took over {:?}", REQUEST_TIMEOUT);
_ = timeout_fut => {
log::error!("cancelled csp request task for {method:?} id {id} which took over {:?}", timeout.unwrap());
anyhow::bail!("Context server request timeout");
}
}
@ -452,6 +451,17 @@ impl Client {
}
}
#[derive(Debug)]
pub struct RequestCanceled;
impl std::error::Error for RequestCanceled {}
impl std::fmt::Display for RequestCanceled {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Context server request was canceled")
}
}
impl fmt::Display for ContextServerId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)

View file

@ -419,7 +419,7 @@ pub struct ToolResponse<T> {
pub structured_content: T,
}
#[derive(Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
struct RawRequest {
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<RequestId>,

View file

@ -5,6 +5,8 @@
//! read/write messages and the types from types.rs for serialization/deserialization
//! of messages.
use std::time::Duration;
use anyhow::Result;
use futures::channel::oneshot;
use gpui::AsyncApp;
@ -98,13 +100,14 @@ impl InitializedContextServerProtocol {
self.inner.request(T::METHOD, params).await
}
pub async fn cancellable_request<T: Request>(
pub async fn request_with<T: Request>(
&self,
params: T::Params,
cancel_rx: oneshot::Receiver<()>,
cancel_rx: Option<oneshot::Receiver<()>>,
timeout: Option<Duration>,
) -> Result<T::Response> {
self.inner
.cancellable_request(T::METHOD, params, cancel_rx)
.request_with(T::METHOD, params, cancel_rx, timeout)
.await
}

View file

@ -626,6 +626,7 @@ pub enum ClientNotification {
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelledParams {
pub request_id: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
@ -685,6 +686,18 @@ pub struct CallToolResponse {
pub structured_content: Option<serde_json::Value>,
}
impl CallToolResponse {
pub fn text_contents(&self) -> String {
let mut text = String::new();
for chunk in &self.content {
if let ToolResponseContent::Text { text: chunk } = chunk {
text.push_str(&chunk)
};
}
text
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ToolResponseContent {