agent_client_protocol/jsonrpc.rs
1//! Core JSON-RPC server support.
2
3use agent_client_protocol_schema::v1::{
4 JsonRpcMessage as VersionedJsonRpcMessage, Notification as RpcNotification,
5 Request as RpcRequest, RequestId, SessionId,
6};
7
8// Types re-exported from crate root
9use serde::ser::SerializeSeq as _;
10use serde::{Deserialize, Serialize};
11use std::any::TypeId;
12use std::collections::HashMap;
13use std::fmt::Debug;
14use std::marker::PhantomData;
15use std::panic::Location;
16use std::pin::pin;
17use std::sync::{
18 Arc, Mutex, Weak,
19 atomic::{AtomicBool, AtomicU8, Ordering},
20};
21use uuid::Uuid;
22
23use futures::FutureExt;
24use futures::channel::{mpsc, oneshot};
25use futures::future::{self, BoxFuture, Either};
26use futures::{AsyncRead, AsyncWrite, StreamExt};
27
28pub(crate) mod close;
29mod dynamic_handler;
30pub(crate) mod handlers;
31mod incoming_actor;
32mod outgoing_actor;
33#[cfg(test)]
34mod prepared_request_tests;
35mod protocol_compat;
36mod raw_error;
37pub(crate) mod run;
38mod task_actor;
39mod termination;
40mod transport_actor;
41
42use crate::jsonrpc::close::{ChainedClose, CloseCallback};
43pub use crate::jsonrpc::close::{HandleConnectionClose, NullClose};
44use crate::jsonrpc::dynamic_handler::DynamicHandlerMessage;
45pub use crate::jsonrpc::handlers::NullHandler;
46use crate::jsonrpc::handlers::{ChainedHandler, NamedHandler};
47use crate::jsonrpc::handlers::{MessageHandler, NotificationHandler, RequestHandler};
48use crate::jsonrpc::outgoing_actor::{OutgoingMessageTx, send_raw_message};
49#[cfg(feature = "unstable_protocol_v2")]
50pub use crate::jsonrpc::protocol_compat::{
51 InitializationVersionMismatch, InitializeOutcome, SelectedInitializeResponse,
52};
53use crate::jsonrpc::protocol_compat::{ProtocolCompat, ProtocolMode};
54#[cfg(feature = "unstable_protocol_v2")]
55pub(crate) use crate::jsonrpc::protocol_compat::{
56 normalize_newer_v2_initialize_request, normalize_v1_initialize_request,
57};
58pub use crate::jsonrpc::raw_error::{RawJsonRpcError, RawJsonRpcResponse};
59use crate::jsonrpc::run::SpawnedRun;
60use crate::jsonrpc::run::{ChainRun, NullRun, RunWithConnectionTo};
61use crate::jsonrpc::task_actor::{Task, TaskTx};
62pub use crate::jsonrpc::termination::{
63 CONNECTION_TERMINATED_REASON, ConnectionTermination, is_connection_terminated,
64};
65use crate::jsonrpc::termination::{
66 TerminationSource, TerminationSources, connection_terminated_error,
67};
68#[cfg(feature = "unstable_mcp_over_acp")]
69use crate::mcp_server::McpServer;
70use crate::role::HasPeer;
71use crate::role::Role;
72use crate::{Agent, Client, ConnectTo, Proxy, RoleId};
73
74/// One valid JSON-RPC message carried inside a [`TransportFrame`].
75///
76/// This uses the JSON-RPC envelope types from `agent-client-protocol-schema`
77/// while keeping method params and response errors protocol-neutral at the
78/// transport boundary.
79#[derive(Debug, Clone)]
80pub enum RawJsonRpcMessage {
81 /// A JSON-RPC request with an id and expected response.
82 Request(RpcRequest<RawJsonRpcParams>),
83 /// A JSON-RPC notification without a response.
84 Notification(RpcNotification<RawJsonRpcParams>),
85 /// A JSON-RPC response to a prior request.
86 Response(RawJsonRpcResponse),
87}
88
89/// A JSON-RPC frame exchanged between protocol components and transports.
90///
91/// A frame preserves the boundary between a single JSON-RPC value and a batch.
92/// Malformed wire input is represented explicitly; transport failures are
93/// reported by the future that drives the transport rather than sent through a
94/// [`Channel`].
95#[derive(Clone, Debug)]
96pub enum TransportFrame {
97 /// One valid JSON-RPC message.
98 Single(RawJsonRpcMessage),
99 /// One malformed or invalid wire value retained for relays.
100 Malformed {
101 /// The original wire representation.
102 raw: String,
103 /// The JSON-RPC error associated with the malformed value.
104 error: crate::Error,
105 },
106 /// Entries retained from one non-empty JSON-RPC batch, kept in source order.
107 Batch(TransportBatch),
108}
109
110/// A structurally non-empty JSON-RPC batch retained across framed relays.
111#[derive(Clone, Debug)]
112pub struct TransportBatch {
113 first: TransportBatchEntry,
114 rest: Vec<TransportBatchEntry>,
115}
116
117/// One entry in a [`TransportBatch`].
118#[derive(Clone, Debug)]
119pub enum TransportBatchEntry {
120 /// A valid JSON-RPC message.
121 Message(RawJsonRpcMessage),
122 /// A malformed or invalid JSON-RPC value retained for relays.
123 Malformed {
124 /// The original JSON value.
125 raw: serde_json::Value,
126 /// The JSON-RPC error associated with the malformed value.
127 error: crate::Error,
128 },
129}
130
131pub(crate) fn is_response_only_shape(value: &serde_json::Value) -> bool {
132 value.as_object().is_some_and(|object| {
133 !object.contains_key("method")
134 && (object.contains_key("result") || object.contains_key("error"))
135 })
136}
137
138pub(crate) fn raw_is_response_only_shape(raw: &str) -> bool {
139 serde_json::from_str(raw).is_ok_and(|value| is_response_only_shape(&value))
140}
141
142impl TransportBatchEntry {
143 /// Create a valid batch entry.
144 #[must_use]
145 pub fn message(message: RawJsonRpcMessage) -> Self {
146 Self::Message(message)
147 }
148
149 /// Create a malformed batch entry.
150 #[must_use]
151 pub fn malformed(raw: serde_json::Value, error: crate::Error) -> Self {
152 Self::Malformed { raw, error }
153 }
154
155 #[cfg(test)]
156 fn as_result(&self) -> Result<&RawJsonRpcMessage, &crate::Error> {
157 match self {
158 Self::Message(message) => Ok(message),
159 Self::Malformed { error, .. } => Err(error),
160 }
161 }
162
163 fn message_ref(&self) -> Option<&RawJsonRpcMessage> {
164 match self {
165 Self::Message(message) => Some(message),
166 Self::Malformed { .. } => None,
167 }
168 }
169}
170
171impl Serialize for TransportBatchEntry {
172 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
173 where
174 S: serde::Serializer,
175 {
176 match self {
177 Self::Message(message) => message.serialize(serializer),
178 Self::Malformed { raw, .. } => raw.serialize(serializer),
179 }
180 }
181}
182
183impl TransportBatch {
184 /// Create a non-empty batch from entries.
185 ///
186 /// Returns `None` when the iterator is empty.
187 pub fn from_entries(entries: impl IntoIterator<Item = TransportBatchEntry>) -> Option<Self> {
188 let mut entries = entries.into_iter();
189 Some(Self {
190 first: entries.next()?,
191 rest: entries.collect(),
192 })
193 }
194
195 /// Create a non-empty batch from valid messages.
196 ///
197 /// Returns `None` when the iterator is empty.
198 pub fn from_messages(messages: impl IntoIterator<Item = RawJsonRpcMessage>) -> Option<Self> {
199 Self::from_entries(messages.into_iter().map(TransportBatchEntry::message))
200 }
201
202 /// Iterate over entries in source order.
203 pub fn entries(&self) -> impl Iterator<Item = &TransportBatchEntry> {
204 std::iter::once(&self.first).chain(&self.rest)
205 }
206
207 /// Iterate mutably over entries in source order.
208 pub fn entries_mut(&mut self) -> impl Iterator<Item = &mut TransportBatchEntry> {
209 std::iter::once(&mut self.first).chain(&mut self.rest)
210 }
211
212 /// Consume this batch and iterate over its entries in source order.
213 pub fn into_entries(self) -> impl Iterator<Item = TransportBatchEntry> {
214 std::iter::once(self.first).chain(self.rest)
215 }
216
217 /// Return the number of entries in this non-empty batch.
218 #[must_use]
219 pub fn len(&self) -> usize {
220 1 + self.rest.len()
221 }
222
223 /// Return whether this batch is empty.
224 ///
225 /// A `TransportBatch` is structurally non-empty, so this always returns
226 /// `false`.
227 #[must_use]
228 pub const fn is_empty(&self) -> bool {
229 false
230 }
231
232 #[cfg(test)]
233 pub(crate) fn iter_results(
234 &self,
235 ) -> impl Iterator<Item = Result<&RawJsonRpcMessage, &crate::Error>> {
236 self.entries().map(TransportBatchEntry::as_result)
237 }
238
239 fn messages(&self) -> impl Iterator<Item = &RawJsonRpcMessage> {
240 self.entries().filter_map(TransportBatchEntry::message_ref)
241 }
242}
243
244impl Serialize for TransportBatch {
245 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
246 where
247 S: serde::Serializer,
248 {
249 let mut sequence = serializer.serialize_seq(Some(1 + self.rest.len()))?;
250 sequence.serialize_element(&self.first)?;
251 for entry in &self.rest {
252 sequence.serialize_element(entry)?;
253 }
254 sequence.end()
255 }
256}
257
258impl TransportFrame {
259 fn inspect_messages(
260 &self,
261 observer: &mut impl FnMut(&RawJsonRpcMessage) -> Result<(), crate::Error>,
262 ) -> Result<(), crate::Error> {
263 match self {
264 Self::Single(message) => observer(message),
265 Self::Malformed { .. } => Ok(()),
266 Self::Batch(batch) => {
267 for message in batch.messages() {
268 observer(message)?;
269 }
270 Ok(())
271 }
272 }
273 }
274}
275
276/// Raw JSON-RPC request or notification parameters.
277///
278/// JSON-RPC params, when present, must be either an array or an object.
279#[derive(Debug, Clone, PartialEq)]
280pub enum RawJsonRpcParams {
281 /// Positional JSON-RPC params.
282 Array(Vec<serde_json::Value>),
283 /// Named JSON-RPC params.
284 Object(serde_json::Map<String, serde_json::Value>),
285}
286
287impl RawJsonRpcParams {
288 /// Convert a JSON value into JSON-RPC params.
289 pub fn from_value(value: serde_json::Value) -> Result<Option<Self>, crate::Error> {
290 match value {
291 serde_json::Value::Null => Ok(None),
292 serde_json::Value::Array(array) => Ok(Some(Self::Array(array))),
293 serde_json::Value::Object(object) => Ok(Some(Self::Object(object))),
294 _ => {
295 Err(crate::Error::invalid_params()
296 .data("JSON-RPC params must be an object or array"))
297 }
298 }
299 }
300
301 /// Convert params back into a JSON value.
302 #[must_use]
303 pub fn into_value(self) -> serde_json::Value {
304 match self {
305 Self::Array(array) => serde_json::Value::Array(array),
306 Self::Object(object) => serde_json::Value::Object(object),
307 }
308 }
309}
310
311impl Serialize for RawJsonRpcParams {
312 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
313 where
314 S: serde::Serializer,
315 {
316 match self {
317 Self::Array(array) => array.serialize(serializer),
318 Self::Object(object) => object.serialize(serializer),
319 }
320 }
321}
322
323impl<'de> Deserialize<'de> for RawJsonRpcParams {
324 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
325 where
326 D: serde::Deserializer<'de>,
327 {
328 let value = serde_json::Value::deserialize(deserializer)?;
329 match value {
330 serde_json::Value::Array(array) => Ok(Self::Array(array)),
331 serde_json::Value::Object(object) => Ok(Self::Object(object)),
332 _ => Err(serde::de::Error::custom(
333 "JSON-RPC params must be an object or array",
334 )),
335 }
336 }
337}
338
339impl RawJsonRpcMessage {
340 /// Build a raw JSON-RPC request message.
341 pub fn request(
342 method: String,
343 params: serde_json::Value,
344 id: RequestId,
345 ) -> Result<Self, crate::Error> {
346 Ok(Self::Request(RpcRequest {
347 id,
348 method: Arc::from(method),
349 params: RawJsonRpcParams::from_value(params)?,
350 }))
351 }
352
353 /// Build a raw JSON-RPC notification message.
354 pub fn notification(method: String, params: serde_json::Value) -> Result<Self, crate::Error> {
355 Ok(Self::Notification(RpcNotification {
356 method: Arc::from(method),
357 params: RawJsonRpcParams::from_value(params)?,
358 }))
359 }
360
361 /// Build a JSON-RPC response from an ACP result.
362 ///
363 /// For other protocols, construct [`RawJsonRpcResponse`] directly so error
364 /// codes and fields are not interpreted as ACP.
365 #[must_use]
366 pub fn response(id: RequestId, response: Result<serde_json::Value, crate::Error>) -> Self {
367 Self::Response(RawJsonRpcResponse::new(
368 id,
369 response.map_err(|error| Box::new(error.into())),
370 ))
371 }
372
373 /// The response id, if this is a response.
374 #[must_use]
375 pub fn response_id(&self) -> Option<&RequestId> {
376 match self {
377 Self::Response(
378 RawJsonRpcResponse::Result { id, .. } | RawJsonRpcResponse::Error { id, .. },
379 ) => Some(id),
380 Self::Request(_) | Self::Notification(_) => None,
381 }
382 }
383}
384
385impl Serialize for RawJsonRpcMessage {
386 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
387 where
388 S: serde::Serializer,
389 {
390 match self {
391 Self::Request(request) => {
392 VersionedJsonRpcMessage::wrap(request.clone()).serialize(serializer)
393 }
394 Self::Notification(notification) => {
395 VersionedJsonRpcMessage::wrap(notification.clone()).serialize(serializer)
396 }
397 Self::Response(response) => {
398 VersionedJsonRpcMessage::wrap(response.clone()).serialize(serializer)
399 }
400 }
401 }
402}
403
404impl<'de> Deserialize<'de> for RawJsonRpcMessage {
405 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
406 where
407 D: serde::Deserializer<'de>,
408 {
409 let value = serde_json::Value::deserialize(deserializer)?;
410 let Some(object) = value.as_object() else {
411 return Err(serde::de::Error::custom("invalid JSON-RPC message"));
412 };
413
414 let has_method = object.contains_key("method");
415 let has_id = object.contains_key("id");
416 let has_result = object.contains_key("result");
417 let has_error = object.contains_key("error");
418
419 if has_method && !has_result && !has_error {
420 if has_id {
421 let request = serde_json::from_value::<
422 VersionedJsonRpcMessage<RpcRequest<RawJsonRpcParams>>,
423 >(value)
424 .map_err(serde::de::Error::custom)?
425 .into_inner();
426 Ok(Self::Request(request))
427 } else {
428 let notification = serde_json::from_value::<
429 VersionedJsonRpcMessage<RpcNotification<RawJsonRpcParams>>,
430 >(value)
431 .map_err(serde::de::Error::custom)?
432 .into_inner();
433 Ok(Self::Notification(notification))
434 }
435 } else if !has_method && has_id && has_result != has_error {
436 let response =
437 serde_json::from_value::<VersionedJsonRpcMessage<RawJsonRpcResponse>>(value)
438 .map_err(serde::de::Error::custom)?
439 .into_inner();
440 Ok(Self::Response(response))
441 } else {
442 Err(serde::de::Error::custom("invalid JSON-RPC message"))
443 }
444 }
445}
446
447fn params_from_transport(params: Option<RawJsonRpcParams>) -> serde_json::Value {
448 params.map_or(serde_json::Value::Null, RawJsonRpcParams::into_value)
449}
450
451/// Handlers process incoming JSON-RPC messages on a connection.
452///
453/// When messages arrive, they flow through a chain of handlers. Each handler can
454/// either **claim** the message (handle it) or **decline** it (pass to the next handler).
455///
456/// # Message Flow
457///
458/// Messages flow through three layers of handlers in order:
459///
460/// ```text
461/// ┌─────────────────────────────────────────────────────────────────┐
462/// │ Incoming Message │
463/// └─────────────────────────────────────────────────────────────────┘
464/// │
465/// ▼
466/// ┌─────────────────────────────────────────────────────────────────┐
467/// │ 1. User Handlers (registered via on_receive_request, etc.) │
468/// │ - Tried in registration order │
469/// │ - First handler to return Handled::Yes claims the message │
470/// └─────────────────────────────────────────────────────────────────┘
471/// │ Handled::No
472/// ▼
473/// ┌─────────────────────────────────────────────────────────────────┐
474/// │ 2. Dynamic Handlers (added at runtime) │
475/// │ - Used for session-specific message handling │
476/// │ - Added via ConnectionTo::add_dynamic_handler │
477/// └─────────────────────────────────────────────────────────────────┘
478/// │ Handled::No
479/// ▼
480/// ┌─────────────────────────────────────────────────────────────────┐
481/// │ 3. Role Default Handler │
482/// │ - Fallback based on the connection's Role │
483/// │ - Handles protocol-level messages (e.g., proxy forwarding) │
484/// └─────────────────────────────────────────────────────────────────┘
485/// │ Handled::No
486/// ▼
487/// ┌─────────────────────────────────────────────────────────────────┐
488/// │ Unhandled: requests error, notifications ignored │
489/// └─────────────────────────────────────────────────────────────────┘
490/// ```
491///
492/// # The `Handled` Return Value
493///
494/// Each handler returns [`Handled`] to indicate whether it processed the message:
495///
496/// - **`Handled::Yes`** - Message was handled. No further handlers are invoked.
497/// - **`Handled::No { message, retry }`** - Message was not handled. The message
498/// (possibly modified) is passed to the next handler in the chain.
499///
500/// For convenience, handlers can return `()` which is equivalent to `Handled::Yes`.
501///
502/// # The Retry Mechanism
503///
504/// The `retry` flag in `Handled::No` controls what happens when no handler claims a message:
505///
506/// - **`retry: false`** (default) - Send a "method not found" error
507/// response immediately for requests, or ignore notifications.
508/// - **`retry: true`** - Queue the message and retry it when new dynamic handlers are added.
509///
510/// This mechanism exists because of a timing issue with sessions: when a `session/new`
511/// response is being processed, the dynamic handler for that session hasn't been registered
512/// yet, but `session/update` notifications for that session may already be arriving.
513/// By setting `retry: true`, these early notifications are queued until the session's
514/// dynamic handler is added.
515///
516/// # Handler Registration
517///
518/// Most users register handlers using the builder methods on [`Builder`]:
519///
520/// ```
521/// # use agent_client_protocol::{Agent, Client, ConnectTo};
522/// # use agent_client_protocol::schema::v1::{AgentCapabilities, InitializeRequest, InitializeResponse};
523/// # use agent_client_protocol_test::StatusUpdate;
524/// # async fn example(transport: impl ConnectTo<Agent>) -> Result<(), agent_client_protocol::Error> {
525/// Agent.builder()
526/// .on_receive_request(async |req: InitializeRequest, responder, cx| {
527/// responder.respond(
528/// InitializeResponse::new(req.protocol_version)
529/// .agent_capabilities(AgentCapabilities::new()),
530/// )
531/// }, agent_client_protocol::on_receive_request!())
532/// .on_receive_notification(async |notif: StatusUpdate, cx| {
533/// // Process notification
534/// Ok(())
535/// }, agent_client_protocol::on_receive_notification!())
536/// .connect_to(transport)
537/// .await?;
538/// # Ok(())
539/// # }
540/// ```
541///
542/// The type parameter on the closure determines which messages are dispatched to it.
543/// Messages that don't match the type are automatically passed to the next handler.
544///
545/// # Implementing Custom Handlers
546///
547/// For advanced use cases, you can implement [`HandleDispatchFrom`] directly:
548///
549/// ```no_run
550/// use agent_client_protocol::{
551/// Client, ConnectionTo, Dispatch, Error, HandleDispatchFrom, Handled,
552/// };
553///
554/// struct MyHandler;
555///
556/// impl HandleDispatchFrom<Client> for MyHandler {
557/// async fn handle_dispatch_from(
558/// &mut self,
559/// message: Dispatch,
560/// _connection: ConnectionTo<Client>,
561/// ) -> Result<Handled<Dispatch>, Error> {
562/// if message.method() == "my/custom/method" {
563/// // Handle it
564/// Ok(Handled::Yes)
565/// } else {
566/// // Pass to next handler
567/// Ok(Handled::No { message, retry: false })
568/// }
569/// }
570///
571/// fn describe_chain(&self) -> impl std::fmt::Debug {
572/// "MyHandler"
573/// }
574/// }
575/// ```
576///
577/// # Important: Handlers Must Not Block
578///
579/// The connection processes messages on a single async task. While a handler is running,
580/// no other messages can be processed. For expensive operations, use [`ConnectionTo::spawn`]
581/// to run work concurrently:
582///
583/// ```
584/// # use agent_client_protocol::{Client, Agent, ConnectTo};
585/// # use agent_client_protocol_test::{expensive_operation, ProcessComplete};
586/// # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
587/// # Client.builder().connect_with(transport, async |cx| {
588/// cx.spawn({
589/// let connection = cx.clone();
590/// async move {
591/// let result = expensive_operation("data").await?;
592/// connection.send_notification(ProcessComplete { result })?;
593/// Ok(())
594/// }
595/// })?;
596/// # Ok(())
597/// # }).await?;
598/// # Ok(())
599/// # }
600/// ```
601#[allow(async_fn_in_trait)]
602/// A handler for incoming JSON-RPC messages.
603///
604/// This trait is implemented by types that can process incoming messages on a connection.
605/// Handlers are registered with a [`Builder`] and are called in order until
606/// one claims the message.
607///
608/// The type parameter is the counterpart role that messages arrive from and
609/// that the supplied [`ConnectionTo`] addresses. An agent handler therefore
610/// implements `HandleDispatchFrom<Client>`, while a client handler implements
611/// `HandleDispatchFrom<Agent>`.
612pub trait HandleDispatchFrom<Counterpart: Role>: Send {
613 /// Attempt to claim an incoming dispatch (request, notification, or response).
614 ///
615 /// # Important: do not block
616 ///
617 /// The server will not process new messages until this handler returns.
618 /// You should avoid blocking in this callback unless you wish to block the server (e.g., for rate limiting).
619 /// The recommended approach to manage expensive operations is to the [`ConnectionTo::spawn`] method available on the message context.
620 ///
621 /// # Parameters
622 ///
623 /// * `message` - The incoming message to handle.
624 /// * `connection` - The connection, used to send messages and access connection state.
625 ///
626 /// # Returns
627 ///
628 /// * `Ok(Handled::Yes)` if the message was claimed. It will not be propagated further.
629 /// * `Ok(Handled::No(message))` if not; the (possibly changed) message will be passed to the remaining handlers.
630 /// * `Err` if processing fails. Requests receive an Error Response, response
631 /// errors are routed to the local request awaiter, and notification errors
632 /// are logged without a wire reply.
633 fn handle_dispatch_from(
634 &mut self,
635 message: Dispatch,
636 connection: ConnectionTo<Counterpart>,
637 ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send;
638
639 /// Returns a debug description of the registered handlers for diagnostics.
640 fn describe_chain(&self) -> impl std::fmt::Debug;
641}
642
643impl<Counterpart: Role, H> HandleDispatchFrom<Counterpart> for &mut H
644where
645 H: HandleDispatchFrom<Counterpart>,
646{
647 fn handle_dispatch_from(
648 &mut self,
649 message: Dispatch,
650 cx: ConnectionTo<Counterpart>,
651 ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
652 H::handle_dispatch_from(self, message, cx)
653 }
654
655 fn describe_chain(&self) -> impl std::fmt::Debug {
656 H::describe_chain(self)
657 }
658}
659
660/// Selects the connection context exposed by a [`Builder`]'s callbacks.
661///
662/// This trait is an implementation detail of the typed builder aliases. It is
663/// public so the callback connection type remains expressible in public API
664/// signatures.
665#[doc(hidden)]
666#[allow(private_bounds)]
667pub trait ConnectionContext: connection_context::Sealed + Send + Sync + 'static {
668 /// The connection type exposed to callbacks for `Counterpart`.
669 type Connection<Counterpart: Role>: Clone + Send + Sync + 'static;
670}
671
672mod connection_context {
673 use super::{ConnectionContext, ConnectionTo, Role};
674
675 pub trait Sealed {
676 fn from_raw<Counterpart: Role>(
677 connection: ConnectionTo<Counterpart>,
678 ) -> <Self as ConnectionContext>::Connection<Counterpart>
679 where
680 Self: ConnectionContext;
681 }
682
683 pub(crate) fn from_raw<Context: ConnectionContext, Counterpart: Role>(
684 connection: ConnectionTo<Counterpart>,
685 ) -> Context::Connection<Counterpart> {
686 <Context as Sealed>::from_raw(connection)
687 }
688}
689
690/// The default callback context used by stable and low-level builders.
691#[doc(hidden)]
692#[derive(Copy, Clone, Debug, Default)]
693pub struct RawConnectionContext;
694
695impl connection_context::Sealed for RawConnectionContext {
696 fn from_raw<Counterpart: Role>(
697 connection: ConnectionTo<Counterpart>,
698 ) -> <Self as ConnectionContext>::Connection<Counterpart> {
699 connection
700 }
701}
702
703impl ConnectionContext for RawConnectionContext {
704 type Connection<Counterpart: Role> = ConnectionTo<Counterpart>;
705}
706
707/// The callback context used by ACP protocol v2 builders.
708#[cfg(feature = "unstable_protocol_v2")]
709#[doc(hidden)]
710#[derive(Copy, Clone, Debug, Default)]
711pub struct V2ConnectionContext;
712
713#[cfg(feature = "unstable_protocol_v2")]
714impl connection_context::Sealed for V2ConnectionContext {
715 fn from_raw<Counterpart: Role>(
716 connection: ConnectionTo<Counterpart>,
717 ) -> <Self as ConnectionContext>::Connection<Counterpart> {
718 V2ConnectionTo { inner: connection }
719 }
720}
721
722#[cfg(feature = "unstable_protocol_v2")]
723impl ConnectionContext for V2ConnectionContext {
724 type Connection<Counterpart: Role> = V2ConnectionTo<Counterpart>;
725}
726
727/// A JSON-RPC connection builder whose callbacks receive [`V2ConnectionTo`].
728#[cfg(feature = "unstable_protocol_v2")]
729pub type V2Builder<Host, Handler = NullHandler, Runner = NullRun, Close = NullClose> =
730 Builder<Host, Handler, Runner, Close, V2ConnectionContext>;
731
732/// A JSON-RPC connection that can act as either a server, client, or both.
733///
734/// [`Builder`] provides a builder-style API for creating JSON-RPC servers and clients.
735/// You start by calling `Role.builder()` (e.g., `Client.builder()`), then add message
736/// handlers, and finally drive the connection with either [`connect_to`](Builder::connect_to)
737/// or [`connect_with`](Builder::connect_with), providing a component implementation
738/// (e.g., [`ByteStreams`] for byte streams).
739///
740/// # JSON-RPC Primer
741///
742/// JSON-RPC 2.0 has two fundamental message types:
743///
744/// * **Requests** - Messages that expect a response. They have an `id` field that gets
745/// echoed back in the response so the sender can correlate them.
746/// * **Notifications** - Fire-and-forget messages with no `id` field. The sender doesn't
747/// expect or receive a response.
748///
749/// # Type-Driven Message Dispatch
750///
751/// The handler registration methods use Rust's type system to determine which messages
752/// to handle. The type parameter you provide controls what gets dispatched to your handler:
753///
754/// ## Single Message Types
755///
756/// The simplest case - handle one specific message type:
757///
758/// ```no_run
759/// # use agent_client_protocol_test::*;
760/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, SessionNotification};
761/// # async fn example() -> Result<(), agent_client_protocol::Error> {
762/// # let connection = mock_connection();
763/// connection
764/// .on_receive_request(async |req: InitializeRequest, responder, cx| {
765/// // Handle only InitializeRequest messages
766/// responder.respond(InitializeResponse::make())
767/// }, agent_client_protocol::on_receive_request!())
768/// .on_receive_notification(async |notif: SessionNotification, cx| {
769/// // Handle only SessionUpdate notifications
770/// Ok(())
771/// }, agent_client_protocol::on_receive_notification!())
772/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
773/// # Ok(())
774/// # }
775/// ```
776///
777/// ## Enum Message Types
778///
779/// You can also handle multiple related messages with a single handler by defining an enum
780/// that implements the appropriate trait ([`JsonRpcRequest`] or [`JsonRpcNotification`]):
781///
782/// ```no_run
783/// # use agent_client_protocol_test::*;
784/// # use agent_client_protocol::{JsonRpcRequest, JsonRpcMessage, UntypedMessage};
785/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, PromptRequest, PromptResponse};
786/// # async fn example() -> Result<(), agent_client_protocol::Error> {
787/// # let connection = mock_connection();
788/// // Define an enum for multiple request types
789/// #[derive(Debug, Clone)]
790/// enum MyRequests {
791/// Initialize(InitializeRequest),
792/// Prompt(PromptRequest),
793/// }
794///
795/// // Implement JsonRpcRequest for your enum
796/// # impl JsonRpcMessage for MyRequests {
797/// # fn matches_method(_method: &str) -> bool { false }
798/// # fn method(&self) -> &str { "myRequests" }
799/// # fn to_untyped_message(&self) -> Result<UntypedMessage, agent_client_protocol::Error> { todo!() }
800/// # fn parse_message(_method: &str, _params: &impl serde::Serialize) -> Result<Self, agent_client_protocol::Error> { Err(agent_client_protocol::Error::method_not_found()) }
801/// # }
802/// impl JsonRpcRequest for MyRequests { type Response = serde_json::Value; }
803///
804/// // Handle all variants in one place
805/// connection.on_receive_request(async |req: MyRequests, responder, cx| {
806/// match req {
807/// MyRequests::Initialize(init) => { responder.respond(serde_json::json!({})) }
808/// MyRequests::Prompt(prompt) => { responder.respond(serde_json::json!({})) }
809/// }
810/// }, agent_client_protocol::on_receive_request!())
811/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
812/// # Ok(())
813/// # }
814/// ```
815///
816/// ## Mixed Message Types
817///
818/// To handle requests, notifications, and responses in one callback, use
819/// [`on_receive_dispatch`](Self::on_receive_dispatch):
820///
821/// ```no_run
822/// # use agent_client_protocol_test::*;
823/// # use agent_client_protocol::Dispatch;
824/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, SessionNotification};
825/// # async fn example() -> Result<(), agent_client_protocol::Error> {
826/// # let connection = mock_connection();
827/// // on_receive_dispatch receives requests, notifications, and responses
828/// connection.on_receive_dispatch(async |msg: Dispatch<InitializeRequest, SessionNotification>, _cx| {
829/// match msg {
830/// Dispatch::Request(req, responder) => {
831/// responder.respond(InitializeResponse::make())
832/// }
833/// Dispatch::Notification(notif) => {
834/// Ok(())
835/// }
836/// Dispatch::Response(result, router) => {
837/// // Forward response to its destination
838/// router.route_with_result(result)
839/// }
840/// }
841/// }, agent_client_protocol::on_receive_dispatch!())
842/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
843/// # Ok(())
844/// # }
845/// ```
846///
847/// # Handler Registration
848///
849/// Register handlers using these methods (listed from most common to most flexible):
850///
851/// * [`on_receive_request`](Self::on_receive_request) - Handle JSON-RPC requests (messages expecting responses)
852/// * [`on_receive_notification`](Self::on_receive_notification) - Handle JSON-RPC notifications (fire-and-forget)
853/// * [`on_receive_dispatch`](Self::on_receive_dispatch) - Handle requests, notifications, and responses in one callback
854/// * [`with_handler`](Self::with_handler) - Low-level primitive for maximum flexibility
855///
856/// ## Handler Ordering
857///
858/// Handlers are tried in the order you register them. The first handler that claims a message
859/// (by matching its type) will process it. Subsequent handlers won't see that message:
860///
861/// ```no_run
862/// # use agent_client_protocol_test::*;
863/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, PromptRequest, PromptResponse};
864/// # async fn example() -> Result<(), agent_client_protocol::Error> {
865/// # let connection = mock_connection();
866/// connection
867/// .on_receive_request(async |req: InitializeRequest, responder, cx| {
868/// // This runs first for InitializeRequest
869/// responder.respond(InitializeResponse::make())
870/// }, agent_client_protocol::on_receive_request!())
871/// .on_receive_request(async |req: PromptRequest, responder, cx| {
872/// // This runs first for PromptRequest
873/// responder.respond(PromptResponse::make())
874/// }, agent_client_protocol::on_receive_request!())
875/// // Unknown requests receive Method not found automatically; unhandled
876/// // notifications are ignored.
877/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
878/// # Ok(())
879/// # }
880/// ```
881///
882/// # Event Loop and Concurrency
883///
884/// Understanding the event loop is critical for writing correct handlers.
885///
886/// ## The Event Loop
887///
888/// [`Builder`] runs all handler callbacks on a single async task - the event loop.
889/// While a handler is running, **the server cannot receive new messages**. This means
890/// any blocking or expensive work in your handlers will stall the entire connection.
891///
892/// To avoid blocking the event loop, use [`ConnectionTo::spawn`] to offload serious
893/// work to concurrent tasks:
894///
895/// ```no_run
896/// # use agent_client_protocol_test::*;
897/// # async fn example() -> Result<(), agent_client_protocol::Error> {
898/// # let connection = mock_connection();
899/// connection.on_receive_request(async |req: AnalyzeRequest, responder, cx| {
900/// // Clone cx for the spawned task
901/// cx.spawn({
902/// let connection = cx.clone();
903/// async move {
904/// let result = expensive_analysis(&req.data).await?;
905/// connection.send_notification(AnalysisComplete { result })?;
906/// Ok(())
907/// }
908/// })?;
909///
910/// // Respond immediately without blocking
911/// responder.respond(AnalysisStarted { job_id: 42 })
912/// }, agent_client_protocol::on_receive_request!())
913/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
914/// # Ok(())
915/// # }
916/// ```
917///
918/// Note that the entire connection runs within one async task, so parallelism must be
919/// managed explicitly using [`spawn`](ConnectionTo::spawn).
920///
921/// ## The Connection Context
922///
923/// Handler callbacks receive a context object (`cx`) for interacting with the connection:
924///
925/// * **For request handlers** - [`Responder<R>`] provides [`respond`](Responder::respond)
926/// to send the response, plus methods to send other messages
927/// * **For notification handlers** - [`ConnectionTo`] provides methods to send messages
928/// and spawn tasks
929///
930/// Both context types support:
931/// * [`send_request`](ConnectionTo::send_request) - Send requests to the other side
932/// * [`send_notification`](ConnectionTo::send_notification) - Send notifications
933/// * [`spawn`](ConnectionTo::spawn) - Run tasks concurrently without blocking the event loop
934///
935/// The [`SentRequest`] returned by `send_request` provides methods like
936/// [`on_receiving_result`](SentRequest::on_receiving_result) that help you
937/// avoid accidentally blocking the event loop while waiting for responses.
938///
939/// # Driving the Connection
940///
941/// After adding handlers, you must drive the connection using one of two modes:
942///
943/// ## Server Mode: `connect_to()`
944///
945/// Use [`connect_to`](Self::connect_to) when you only need to respond to incoming messages:
946///
947/// ```no_run
948/// # use agent_client_protocol_test::*;
949/// # async fn example() -> Result<(), agent_client_protocol::Error> {
950/// # let connection = mock_connection();
951/// connection
952/// .on_receive_request(async |req: MyRequest, responder, cx| {
953/// responder.respond(MyResponse { status: "ok".into() })
954/// }, agent_client_protocol::on_receive_request!())
955/// .connect_to(MockTransport) // Runs until connection closes or error occurs
956/// .await?;
957/// # Ok(())
958/// # }
959/// ```
960///
961/// The connection will process incoming messages and invoke your handlers until the
962/// connection is closed or an error occurs.
963///
964/// ## Client Mode: `connect_with()`
965///
966/// Use [`connect_with`](Self::connect_with) when you need to both handle incoming messages
967/// AND send your own requests/notifications:
968///
969/// ```no_run
970/// # use agent_client_protocol_test::*;
971/// # use agent_client_protocol::schema::v1::InitializeRequest;
972/// # async fn example() -> Result<(), agent_client_protocol::Error> {
973/// # let connection = mock_connection();
974/// connection
975/// .on_receive_request(async |req: MyRequest, responder, cx| {
976/// responder.respond(MyResponse { status: "ok".into() })
977/// }, agent_client_protocol::on_receive_request!())
978/// .connect_with(MockTransport, async |cx| {
979/// // You can send requests to the other side
980/// let response = cx.send_request(InitializeRequest::make())
981/// .block_task()
982/// .await?;
983///
984/// // And send notifications
985/// cx.send_notification(StatusUpdate { message: "ready".into() })?;
986///
987/// Ok(())
988/// })
989/// .await?;
990/// # Ok(())
991/// # }
992/// ```
993///
994/// The connection will serve incoming messages in the background while your client closure
995/// runs. When the closure returns, the connection shuts down.
996///
997/// # Example: Complete Agent
998///
999/// ```no_run
1000/// # use agent_client_protocol::UntypedRole;
1001/// # use agent_client_protocol::{Builder};
1002/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, PromptRequest, PromptResponse, SessionNotification};
1003/// # async fn example(transport: impl agent_client_protocol::ConnectTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
1004///
1005/// UntypedRole.builder()
1006/// .name("my-agent") // Optional: for debugging logs
1007/// .on_receive_request(async |init: InitializeRequest, responder, cx| {
1008/// let response: InitializeResponse = todo!();
1009/// responder.respond(response)
1010/// }, agent_client_protocol::on_receive_request!())
1011/// .on_receive_request(async |prompt: PromptRequest, responder, cx| {
1012/// // You can send notifications while processing a request
1013/// let notif: SessionNotification = todo!();
1014/// cx.send_notification(notif)?;
1015///
1016/// // Then respond to the request
1017/// let response: PromptResponse = todo!();
1018/// responder.respond(response)
1019/// }, agent_client_protocol::on_receive_request!())
1020/// .connect_to(transport)
1021/// .await?;
1022/// # Ok(())
1023/// # }
1024/// ```
1025#[must_use]
1026#[derive(Debug)]
1027pub struct Builder<
1028 Host: Role,
1029 Handler = NullHandler,
1030 Runner = NullRun,
1031 Close = NullClose,
1032 Context = RawConnectionContext,
1033> where
1034 Handler: HandleDispatchFrom<Host::Counterpart>,
1035 Runner: RunWithConnectionTo<Host::Counterpart>,
1036 Close: HandleConnectionClose<Host::Counterpart>,
1037 Context: ConnectionContext,
1038{
1039 /// My role.
1040 host: Host,
1041
1042 /// Name of the connection, used in tracing logs.
1043 name: Option<String>,
1044
1045 /// Handler for incoming messages.
1046 handler: Handler,
1047
1048 /// Runner for background connection tasks.
1049 runner: Runner,
1050
1051 /// Protocol version mode for the public API and wire compatibility layer.
1052 protocol_mode: ProtocolMode,
1053
1054 /// Handler run when the incoming transport reaches clean EOF.
1055 on_close: Close,
1056
1057 /// Selects the connection type exposed to user callbacks.
1058 context: PhantomData<fn() -> Context>,
1059}
1060
1061fn default_protocol_mode<Host: Role>() -> ProtocolMode {
1062 let role = TypeId::of::<Host>();
1063
1064 if role == TypeId::of::<Agent>() {
1065 ProtocolMode::v1_agent()
1066 } else if role == TypeId::of::<Client>() {
1067 ProtocolMode::v1_client()
1068 } else if role == TypeId::of::<Proxy>() {
1069 ProtocolMode::v1_proxy()
1070 } else {
1071 ProtocolMode::disabled()
1072 }
1073}
1074
1075impl<Host: Role> Builder<Host, NullHandler, NullRun, NullClose> {
1076 /// Create a new connection builder for the given role.
1077 /// This type follows a builder pattern; use other methods to configure and then invoke
1078 /// [`Self::connect_to`] (to use as a server) or [`Self::connect_with`] to use as a client.
1079 pub fn new(role: Host) -> Self {
1080 Self {
1081 host: role,
1082 name: None,
1083 handler: NullHandler,
1084 runner: NullRun,
1085 protocol_mode: default_protocol_mode::<Host>(),
1086 on_close: NullClose,
1087 context: PhantomData,
1088 }
1089 }
1090}
1091
1092impl<Host: Role, Handler> Builder<Host, Handler, NullRun, NullClose>
1093where
1094 Handler: HandleDispatchFrom<Host::Counterpart>,
1095{
1096 /// Create a new connection builder with the given handler.
1097 pub fn new_with(role: Host, handler: Handler) -> Self {
1098 Self {
1099 host: role,
1100 name: None,
1101 handler,
1102 runner: NullRun,
1103 protocol_mode: default_protocol_mode::<Host>(),
1104 on_close: NullClose,
1105 context: PhantomData,
1106 }
1107 }
1108}
1109
1110#[cfg(feature = "unstable_protocol_v2")]
1111impl<
1112 Host: Role,
1113 Handler: HandleDispatchFrom<Host::Counterpart>,
1114 Runner: RunWithConnectionTo<Host::Counterpart>,
1115 Close: HandleConnectionClose<Host::Counterpart>,
1116> Builder<Host, Handler, Runner, Close>
1117{
1118 pub(crate) fn v2_agent(self) -> V2Builder<Host, Handler, Runner, Close> {
1119 Builder {
1120 host: self.host,
1121 name: self.name,
1122 handler: self.handler,
1123 runner: self.runner,
1124 protocol_mode: ProtocolMode::v2_agent(),
1125 on_close: self.on_close,
1126 context: PhantomData,
1127 }
1128 }
1129
1130 pub(crate) fn v2_client(self) -> V2Builder<Host, Handler, Runner, Close> {
1131 Builder {
1132 host: self.host,
1133 name: self.name,
1134 handler: self.handler,
1135 runner: self.runner,
1136 protocol_mode: ProtocolMode::v2_client(),
1137 on_close: self.on_close,
1138 context: PhantomData,
1139 }
1140 }
1141
1142 pub(crate) fn v2_proxy(self) -> V2Builder<Host, Handler, Runner, Close> {
1143 Builder {
1144 host: self.host,
1145 name: self.name,
1146 handler: self.handler,
1147 runner: self.runner,
1148 protocol_mode: ProtocolMode::v2_proxy(),
1149 on_close: self.on_close,
1150 context: PhantomData,
1151 }
1152 }
1153
1154 /// Disable all automatic ACP protocol-version tracking and validation.
1155 ///
1156 /// This is a low-level escape hatch for protocol-routing infrastructure
1157 /// that inspects and validates raw initialize requests and responses
1158 /// itself before selecting a version-specific implementation. It also
1159 /// disables the version checks applied to messages after initialization.
1160 ///
1161 /// This method is deliberately available only on builders whose callbacks
1162 /// receive raw [`ConnectionTo`] values. Applications should normally use
1163 /// [`Client::builder`](crate::Client::builder),
1164 /// [`Agent::builder`](crate::Agent::builder),
1165 /// [`Proxy::builder`](crate::Proxy::builder), [`Client::v2`](crate::Client::v2),
1166 /// [`Agent::v2`](crate::Agent::v2), or [`Proxy::v2`](crate::Proxy::v2)
1167 /// instead.
1168 ///
1169 /// ```compile_fail
1170 /// # use agent_client_protocol::Client;
1171 /// let _ = Client.v2().without_acp_version_guard();
1172 /// ```
1173 pub fn without_acp_version_guard(mut self) -> Self {
1174 self.protocol_mode = ProtocolMode::disabled();
1175 self
1176 }
1177}
1178
1179#[cfg(feature = "unstable_protocol_v2")]
1180impl<
1181 Handler: HandleDispatchFrom<Agent>,
1182 Runner: RunWithConnectionTo<Agent>,
1183 Close: HandleConnectionClose<Agent>,
1184> Builder<Client, Handler, Runner, Close>
1185{
1186 /// Apply protocol-v2 wire validation while retaining raw callback contexts.
1187 ///
1188 /// This is intended for protocol-routing infrastructure that has already
1189 /// selected v2 but still needs protocol-neutral [`ConnectionTo`] values in
1190 /// its callbacks. The guarded child must still send and receive the
1191 /// `initialize` round trip; a router that consumes initialization itself
1192 /// must use [`Builder::without_acp_version_guard`] for the selected child.
1193 /// Most clients should use [`Client::v2`](crate::Client::v2), which also
1194 /// exposes the version-typed [`V2ConnectionTo`] API.
1195 pub fn with_v2_protocol_guard(mut self) -> Self {
1196 self.protocol_mode = ProtocolMode::v2_client();
1197 self
1198 }
1199}
1200
1201#[cfg(feature = "unstable_protocol_v2")]
1202impl<
1203 Handler: HandleDispatchFrom<Client>,
1204 Runner: RunWithConnectionTo<Client>,
1205 Close: HandleConnectionClose<Client>,
1206> Builder<Agent, Handler, Runner, Close>
1207{
1208 /// Apply protocol-v2 wire validation while retaining raw callback contexts.
1209 ///
1210 /// This is intended for protocol-routing infrastructure that has already
1211 /// selected v2 but still needs protocol-neutral [`ConnectionTo`] values in
1212 /// its callbacks. The guarded child must still receive and answer the
1213 /// `initialize` request; a router that consumes initialization itself must
1214 /// use [`Builder::without_acp_version_guard`] for the selected child. Most
1215 /// agents should use [`Agent::v2`](crate::Agent::v2), which also exposes the
1216 /// version-typed [`V2ConnectionTo`] API.
1217 pub fn with_v2_protocol_guard(mut self) -> Self {
1218 self.protocol_mode = ProtocolMode::v2_agent();
1219 self
1220 }
1221}
1222
1223impl<
1224 Host: Role,
1225 Handler: HandleDispatchFrom<Host::Counterpart>,
1226 Runner: RunWithConnectionTo<Host::Counterpart>,
1227 Close: HandleConnectionClose<Host::Counterpart>,
1228 Context: ConnectionContext,
1229> Builder<Host, Handler, Runner, Close, Context>
1230{
1231 /// Set the "name" of this connection -- used only for debugging logs.
1232 pub fn name(mut self, name: impl ToString) -> Self {
1233 self.name = Some(name.to_string());
1234 self
1235 }
1236
1237 pub(crate) fn v1_agent(mut self) -> Self {
1238 self.protocol_mode = ProtocolMode::v1_agent();
1239 self
1240 }
1241
1242 /// Record a request handler of type `Req`.
1243 ///
1244 /// A v1 agent that handles `initialize` itself rejects other requests
1245 /// until initialization completes, as v1 agents SHOULD. Agents without
1246 /// such a handler, including those with only catch-all handlers, keep
1247 /// accepting requests without initialization, because they could not
1248 /// complete one.
1249 fn note_request_handler<Req: JsonRpcRequest>(mut self) -> Self {
1250 // A handler for every method is a catch-all, not an initialize handler.
1251 const UNRELATED_METHOD: &str = "\0acp-sdk/not-a-method";
1252 if TypeId::of::<Host>() == TypeId::of::<Agent>()
1253 && Req::matches_method("initialize")
1254 && !Req::matches_method(UNRELATED_METHOD)
1255 {
1256 self.protocol_mode = self.protocol_mode.require_initialize();
1257 }
1258 self
1259 }
1260
1261 pub(crate) fn v1_client(mut self) -> Self {
1262 self.protocol_mode = ProtocolMode::v1_client();
1263 self
1264 }
1265
1266 /// Merge another [`Builder`] into this one.
1267 ///
1268 /// Prefer [`Self::on_receive_request`] or [`Self::on_receive_notification`].
1269 /// This is a low-level method that is not intended for general use.
1270 pub fn with_connection_builder(
1271 self,
1272 other: Builder<
1273 Host,
1274 impl HandleDispatchFrom<Host::Counterpart>,
1275 impl RunWithConnectionTo<Host::Counterpart>,
1276 impl HandleConnectionClose<Host::Counterpart>,
1277 Context,
1278 >,
1279 ) -> Builder<
1280 Host,
1281 impl HandleDispatchFrom<Host::Counterpart>,
1282 impl RunWithConnectionTo<Host::Counterpart>,
1283 impl HandleConnectionClose<Host::Counterpart>,
1284 Context,
1285 > {
1286 let Builder {
1287 name: other_name,
1288 handler: other_handler,
1289 runner: other_runner,
1290 protocol_mode: other_protocol_mode,
1291 on_close: other_on_close,
1292 context: _,
1293 host: _,
1294 } = other;
1295 Builder {
1296 host: self.host,
1297 name: self.name,
1298 handler: ChainedHandler::new(
1299 self.handler,
1300 NamedHandler::new(other_name, other_handler),
1301 ),
1302 runner: ChainRun::new(self.runner, other_runner),
1303 protocol_mode: self.protocol_mode.merge(other_protocol_mode),
1304 on_close: ChainedClose::new(self.on_close, other_on_close),
1305 context: PhantomData,
1306 }
1307 }
1308
1309 /// Add a new [`HandleDispatchFrom`] to the chain.
1310 ///
1311 /// Prefer [`Self::on_receive_request`] or [`Self::on_receive_notification`].
1312 /// This is a low-level method that is not intended for general use.
1313 pub fn with_handler(
1314 self,
1315 handler: impl HandleDispatchFrom<Host::Counterpart>,
1316 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context> {
1317 Builder {
1318 host: self.host,
1319 name: self.name,
1320 handler: ChainedHandler::new(self.handler, handler),
1321 runner: self.runner,
1322 protocol_mode: self.protocol_mode,
1323 on_close: self.on_close,
1324 context: PhantomData,
1325 }
1326 }
1327
1328 /// Add a new [`RunWithConnectionTo`] to the chain.
1329 pub fn with_runner<Run1>(
1330 self,
1331 runner: Run1,
1332 ) -> Builder<Host, Handler, impl RunWithConnectionTo<Host::Counterpart>, Close, Context>
1333 where
1334 Run1: RunWithConnectionTo<Host::Counterpart>,
1335 {
1336 Builder {
1337 host: self.host,
1338 name: self.name,
1339 handler: self.handler,
1340 runner: ChainRun::new(self.runner, runner),
1341 protocol_mode: self.protocol_mode,
1342 on_close: self.on_close,
1343 context: PhantomData,
1344 }
1345 }
1346
1347 /// Enqueue a task to run once the connection is actively serving traffic.
1348 #[track_caller]
1349 pub fn with_spawned<F, Fut>(
1350 self,
1351 task: F,
1352 ) -> Builder<Host, Handler, impl RunWithConnectionTo<Host::Counterpart>, Close, Context>
1353 where
1354 F: FnOnce(Context::Connection<Host::Counterpart>) -> Fut + Send,
1355 Fut: Future<Output = Result<(), crate::Error>> + Send,
1356 {
1357 let location = Location::caller();
1358 self.with_runner(SpawnedRun::<_, Context>::new(location, task))
1359 }
1360
1361 /// Run a callback when the incoming transport reaches clean EOF.
1362 ///
1363 /// Each callback runs at most once and receives the connection context. A
1364 /// successful callback observes the close without otherwise changing the
1365 /// lifetime of [`connect_with`](Self::connect_with). Returning an error
1366 /// shuts down the connection and cancels a still-running `connect_with`
1367 /// future.
1368 ///
1369 /// Multiple callbacks run sequentially in registration order. All of them
1370 /// run even if an earlier callback fails, after which the first error is
1371 /// returned. Pending requests are failed before callbacks begin, while the
1372 /// selected connection context's `incoming_closed` future completes only
1373 /// after they finish. A callback must therefore not await that close
1374 /// future itself.
1375 ///
1376 /// This separation lets applications choose their cancellation policy. A
1377 /// callback can notify application-owned tasks and return `Ok(())` for
1378 /// graceful cleanup, or return an error to stop them immediately.
1379 ///
1380 /// Close callbacks do not run for transport or handler errors, foreground
1381 /// completion, or a dropped driver. Use [`ConnectionTo::terminated`] to
1382 /// observe every way the connection can end.
1383 ///
1384 /// ```
1385 /// # use agent_client_protocol::{Client, ConnectTo, Error};
1386 /// # async fn example(transport: impl ConnectTo<Client>) -> Result<(), Error> {
1387 /// Client.builder()
1388 /// .on_close(async |_cx| {
1389 /// Err(Error::internal_error().data("agent transport closed"))
1390 /// })
1391 /// .connect_with(transport, async |_cx| {
1392 /// std::future::pending().await
1393 /// })
1394 /// .await?;
1395 /// # Ok(())
1396 /// # }
1397 /// ```
1398 pub fn on_close<F, Fut>(
1399 self,
1400 callback: F,
1401 ) -> Builder<Host, Handler, Runner, impl HandleConnectionClose<Host::Counterpart>, Context>
1402 where
1403 F: FnOnce(Context::Connection<Host::Counterpart>) -> Fut + Send,
1404 Fut: Future<Output = Result<(), crate::Error>> + Send,
1405 {
1406 Builder {
1407 host: self.host,
1408 name: self.name,
1409 handler: self.handler,
1410 runner: self.runner,
1411 protocol_mode: self.protocol_mode,
1412 on_close: ChainedClose::new(self.on_close, CloseCallback::<_, Context>::new(callback)),
1413 context: PhantomData,
1414 }
1415 }
1416
1417 /// Register a handler for requests, notifications, and responses.
1418 ///
1419 /// Use this when you want to handle all JSON-RPC message kinds in one callback.
1420 /// Your handler receives a [`Dispatch<Req, Notif>`] with three variants:
1421 ///
1422 /// - `Dispatch::Request(request, responder)` - A request with its response context
1423 /// - `Dispatch::Notification(notification)` - A notification
1424 /// - `Dispatch::Response(result, router)` - A response to a request we sent
1425 ///
1426 /// # Example
1427 ///
1428 /// ```no_run
1429 /// # use agent_client_protocol_test::*;
1430 /// # use agent_client_protocol::Dispatch;
1431 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
1432 /// # let connection = mock_connection();
1433 /// connection.on_receive_dispatch(async |message: Dispatch<MyRequest, StatusUpdate>, _cx| {
1434 /// match message {
1435 /// Dispatch::Request(req, responder) => {
1436 /// // Handle request and send response
1437 /// responder.respond(MyResponse { status: "ok".into() })
1438 /// }
1439 /// Dispatch::Notification(notif) => {
1440 /// // Handle notification (no response needed)
1441 /// Ok(())
1442 /// }
1443 /// Dispatch::Response(result, router) => {
1444 /// // Forward response to its destination
1445 /// router.route_with_result(result)
1446 /// }
1447 /// }
1448 /// }, agent_client_protocol::on_receive_dispatch!())
1449 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
1450 /// # Ok(())
1451 /// # }
1452 /// ```
1453 ///
1454 /// For most use cases, prefer [`on_receive_request`](Self::on_receive_request) or
1455 /// [`on_receive_notification`](Self::on_receive_notification) which provide cleaner APIs
1456 /// for handling requests or notifications separately.
1457 ///
1458 /// # Ordering
1459 ///
1460 /// This callback runs inside the dispatch loop and blocks further message processing
1461 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1462 /// ordering guarantees and how to avoid deadlocks.
1463 pub fn on_receive_dispatch<Req, Notif, F, T, ToFut>(
1464 self,
1465 op: F,
1466 to_future_hack: ToFut,
1467 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1468 where
1469 Host::Counterpart: HasPeer<Host::Counterpart>,
1470 Req: JsonRpcRequest,
1471 Notif: JsonRpcNotification,
1472 F: AsyncFnMut(
1473 Dispatch<Req, Notif>,
1474 Context::Connection<Host::Counterpart>,
1475 ) -> Result<T, crate::Error>
1476 + Send,
1477 T: IntoHandled<Dispatch<Req, Notif>>,
1478 ToFut: Fn(
1479 &mut F,
1480 Dispatch<Req, Notif>,
1481 Context::Connection<Host::Counterpart>,
1482 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1483 + Send
1484 + Sync,
1485 {
1486 let handler = MessageHandler::<_, _, _, _, _, _, Context>::new(
1487 self.host.counterpart(),
1488 self.host.counterpart(),
1489 op,
1490 to_future_hack,
1491 );
1492 self.note_request_handler::<Req>().with_handler(handler)
1493 }
1494
1495 /// Register a handler for JSON-RPC requests of type `Req`.
1496 ///
1497 /// Your handler receives three arguments:
1498 /// 1. The request (type `Req`)
1499 /// 2. A [`Responder<Req::Response>`] for sending the response
1500 /// 3. The builder-selected connection context for the peer that sent the
1501 /// request (`ConnectionTo` by default, or `V2ConnectionTo` for a
1502 /// `V2Builder`)
1503 ///
1504 /// The request context allows you to:
1505 /// - Send the response with [`Responder::respond`]
1506 /// - Send notifications to the client with the context's
1507 /// `send_notification` method
1508 /// - Send requests to the client with the context's `send_request` method
1509 ///
1510 /// # Example
1511 ///
1512 /// ```no_run
1513 /// # use agent_client_protocol::{Agent, ConnectTo};
1514 /// # use agent_client_protocol::schema::v1::{PromptRequest, PromptResponse, SessionNotification};
1515 /// # async fn example(transport: impl ConnectTo<Agent>) -> Result<(), agent_client_protocol::Error> {
1516 /// Agent.builder().on_receive_request(async |request: PromptRequest, responder, cx| {
1517 /// // Send a notification while processing
1518 /// let notif: SessionNotification = todo!();
1519 /// cx.send_notification(notif)?;
1520 ///
1521 /// // Send the response
1522 /// let response: PromptResponse = todo!();
1523 /// responder.respond(response)
1524 /// }, agent_client_protocol::on_receive_request!())
1525 /// .connect_to(transport)
1526 /// .await
1527 /// # }
1528 /// ```
1529 ///
1530 /// # Type Parameter
1531 ///
1532 /// `Req` can be either a single request type or an enum of multiple request types.
1533 /// See the [type-driven dispatch](Self#type-driven-message-dispatch) section for details.
1534 ///
1535 /// # Ordering
1536 ///
1537 /// This callback runs inside the dispatch loop and blocks further message processing
1538 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1539 /// ordering guarantees and how to avoid deadlocks.
1540 pub fn on_receive_request<Req: JsonRpcRequest, F, T, ToFut>(
1541 self,
1542 op: F,
1543 to_future_hack: ToFut,
1544 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1545 where
1546 Host::Counterpart: HasPeer<Host::Counterpart>,
1547 F: AsyncFnMut(
1548 Req,
1549 Responder<Req::Response>,
1550 Context::Connection<Host::Counterpart>,
1551 ) -> Result<T, crate::Error>
1552 + Send,
1553 T: IntoHandled<(Req, Responder<Req::Response>)>,
1554 ToFut: Fn(
1555 &mut F,
1556 Req,
1557 Responder<Req::Response>,
1558 Context::Connection<Host::Counterpart>,
1559 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1560 + Send
1561 + Sync,
1562 {
1563 let handler = RequestHandler::<_, _, _, _, _, Context>::new(
1564 self.host.counterpart(),
1565 self.host.counterpart(),
1566 op,
1567 to_future_hack,
1568 );
1569 self.note_request_handler::<Req>().with_handler(handler)
1570 }
1571
1572 /// Register a handler for JSON-RPC notifications of type `Notif`.
1573 ///
1574 /// Notifications are fire-and-forget messages that don't expect a response.
1575 /// Your handler receives:
1576 /// 1. The notification (type `Notif`)
1577 /// 2. The builder-selected connection context for sending messages to the
1578 /// other side
1579 ///
1580 /// Unlike request handlers, you cannot send a response (notifications don't have IDs),
1581 /// but you can still send your own requests and notifications using the context.
1582 ///
1583 /// # Example
1584 ///
1585 /// ```no_run
1586 /// # use agent_client_protocol_test::*;
1587 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
1588 /// # let connection = mock_connection();
1589 /// connection.on_receive_notification(async |notif: SessionUpdate, cx| {
1590 /// // Process the notification
1591 /// update_session_state(¬if)?;
1592 ///
1593 /// // Optionally send a notification back
1594 /// cx.send_notification(StatusUpdate {
1595 /// message: "Acknowledged".into(),
1596 /// })?;
1597 ///
1598 /// Ok(())
1599 /// }, agent_client_protocol::on_receive_notification!())
1600 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
1601 /// # Ok(())
1602 /// # }
1603 /// ```
1604 ///
1605 /// # Type Parameter
1606 ///
1607 /// `Notif` can be either a single notification type or an enum of multiple notification types.
1608 /// See the [type-driven dispatch](Self#type-driven-message-dispatch) section for details.
1609 ///
1610 /// # Ordering
1611 ///
1612 /// This callback runs inside the dispatch loop and blocks further message processing
1613 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1614 /// ordering guarantees and how to avoid deadlocks.
1615 pub fn on_receive_notification<Notif, F, T, ToFut>(
1616 self,
1617 op: F,
1618 to_future_hack: ToFut,
1619 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1620 where
1621 Host::Counterpart: HasPeer<Host::Counterpart>,
1622 Notif: JsonRpcNotification,
1623 F: AsyncFnMut(Notif, Context::Connection<Host::Counterpart>) -> Result<T, crate::Error>
1624 + Send,
1625 T: IntoHandled<(Notif, Context::Connection<Host::Counterpart>)>,
1626 ToFut: Fn(
1627 &mut F,
1628 Notif,
1629 Context::Connection<Host::Counterpart>,
1630 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1631 + Send
1632 + Sync,
1633 {
1634 let handler = NotificationHandler::<_, _, _, _, _, Context>::new(
1635 self.host.counterpart(),
1636 self.host.counterpart(),
1637 op,
1638 to_future_hack,
1639 );
1640 self.with_handler(handler)
1641 }
1642
1643 /// Register a handler for messages from a specific peer.
1644 ///
1645 /// This is similar to [`on_receive_dispatch`](Self::on_receive_dispatch), but allows
1646 /// specifying the source peer explicitly. This is useful when receiving messages
1647 /// from a peer that requires message transformation (e.g., unwrapping `SuccessorMessage`
1648 /// envelopes when receiving from an agent via a proxy).
1649 ///
1650 /// For the common case of receiving from the default counterpart, use
1651 /// [`on_receive_dispatch`](Self::on_receive_dispatch) instead.
1652 ///
1653 /// # Ordering
1654 ///
1655 /// This callback runs inside the dispatch loop and blocks further message processing
1656 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1657 /// ordering guarantees and how to avoid deadlocks.
1658 pub fn on_receive_dispatch_from<
1659 Req: JsonRpcRequest,
1660 Notif: JsonRpcNotification,
1661 Peer: Role,
1662 F,
1663 T,
1664 ToFut,
1665 >(
1666 self,
1667 peer: Peer,
1668 op: F,
1669 to_future_hack: ToFut,
1670 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1671 where
1672 Host::Counterpart: HasPeer<Peer>,
1673 F: AsyncFnMut(
1674 Dispatch<Req, Notif>,
1675 Context::Connection<Host::Counterpart>,
1676 ) -> Result<T, crate::Error>
1677 + Send,
1678 T: IntoHandled<Dispatch<Req, Notif>>,
1679 ToFut: Fn(
1680 &mut F,
1681 Dispatch<Req, Notif>,
1682 Context::Connection<Host::Counterpart>,
1683 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1684 + Send
1685 + Sync,
1686 {
1687 let handler = MessageHandler::<_, _, _, _, _, _, Context>::new(
1688 self.host.counterpart(),
1689 peer,
1690 op,
1691 to_future_hack,
1692 );
1693 self.note_request_handler::<Req>().with_handler(handler)
1694 }
1695
1696 /// Register a handler for JSON-RPC requests from a specific peer.
1697 ///
1698 /// This is similar to [`on_receive_request`](Self::on_receive_request), but allows
1699 /// specifying the source peer explicitly. This is useful when receiving messages
1700 /// from a peer that requires message transformation (e.g., unwrapping `SuccessorRequest`
1701 /// envelopes when receiving from an agent via a proxy).
1702 ///
1703 /// For the common case of receiving from the default counterpart, use
1704 /// [`on_receive_request`](Self::on_receive_request) instead.
1705 ///
1706 /// # Example
1707 ///
1708 /// ```ignore
1709 /// use agent_client_protocol::Agent;
1710 /// use agent_client_protocol::schema::v1::InitializeRequest;
1711 ///
1712 /// // Conductor receiving from agent direction - messages will be unwrapped from SuccessorMessage
1713 /// connection.on_receive_request_from(Agent, async |req: InitializeRequest, responder, cx| {
1714 /// // Handle the request
1715 /// responder.respond(InitializeResponse::make())
1716 /// })
1717 /// ```
1718 ///
1719 /// # Ordering
1720 ///
1721 /// This callback runs inside the dispatch loop and blocks further message processing
1722 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1723 /// ordering guarantees and how to avoid deadlocks.
1724 pub fn on_receive_request_from<Req: JsonRpcRequest, Peer: Role, F, T, ToFut>(
1725 self,
1726 peer: Peer,
1727 op: F,
1728 to_future_hack: ToFut,
1729 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1730 where
1731 Host::Counterpart: HasPeer<Peer>,
1732 F: AsyncFnMut(
1733 Req,
1734 Responder<Req::Response>,
1735 Context::Connection<Host::Counterpart>,
1736 ) -> Result<T, crate::Error>
1737 + Send,
1738 T: IntoHandled<(Req, Responder<Req::Response>)>,
1739 ToFut: Fn(
1740 &mut F,
1741 Req,
1742 Responder<Req::Response>,
1743 Context::Connection<Host::Counterpart>,
1744 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1745 + Send
1746 + Sync,
1747 {
1748 let handler = RequestHandler::<_, _, _, _, _, Context>::new(
1749 self.host.counterpart(),
1750 peer,
1751 op,
1752 to_future_hack,
1753 );
1754 self.note_request_handler::<Req>().with_handler(handler)
1755 }
1756
1757 /// Register a handler for JSON-RPC notifications from a specific peer.
1758 ///
1759 /// This is similar to [`on_receive_notification`](Self::on_receive_notification), but allows
1760 /// specifying the source peer explicitly. This is useful when receiving messages
1761 /// from a peer that requires message transformation (e.g., unwrapping `SuccessorNotification`
1762 /// envelopes when receiving from an agent via a proxy).
1763 ///
1764 /// For the common case of receiving from the default counterpart, use
1765 /// [`on_receive_notification`](Self::on_receive_notification) instead.
1766 ///
1767 /// # Ordering
1768 ///
1769 /// This callback runs inside the dispatch loop and blocks further message processing
1770 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1771 /// ordering guarantees and how to avoid deadlocks.
1772 pub fn on_receive_notification_from<Notif: JsonRpcNotification, Peer: Role, F, T, ToFut>(
1773 self,
1774 peer: Peer,
1775 op: F,
1776 to_future_hack: ToFut,
1777 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1778 where
1779 Host::Counterpart: HasPeer<Peer>,
1780 F: AsyncFnMut(Notif, Context::Connection<Host::Counterpart>) -> Result<T, crate::Error>
1781 + Send,
1782 T: IntoHandled<(Notif, Context::Connection<Host::Counterpart>)>,
1783 ToFut: Fn(
1784 &mut F,
1785 Notif,
1786 Context::Connection<Host::Counterpart>,
1787 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1788 + Send
1789 + Sync,
1790 {
1791 let handler = NotificationHandler::<_, _, _, _, _, Context>::new(
1792 self.host.counterpart(),
1793 peer,
1794 op,
1795 to_future_hack,
1796 );
1797 self.with_handler(handler)
1798 }
1799
1800 /// Run in server mode with the provided transport.
1801 ///
1802 /// This drives the connection by continuously processing messages from the transport
1803 /// and dispatching them to your registered handlers. The connection will run until:
1804 /// - The transport closes (e.g., EOF on byte streams)
1805 /// - An error occurs
1806 ///
1807 /// Handler errors are normally contained: requests receive an Error Response,
1808 /// response-handler errors are routed to the pending local request, and
1809 /// notification errors are logged without a wire reply.
1810 ///
1811 /// On clean EOF, messages already accepted by the outgoing queue—including
1812 /// handler responses and close-callback notifications—are drained through
1813 /// the transport sink before this returns `Ok(())`.
1814 ///
1815 /// The transport boundary carries [`TransportFrame`] values. Physical stream adapters
1816 /// serialize and deserialize frames, while channel-based components relay them directly.
1817 ///
1818 /// Use this mode when you only need to respond to incoming messages and don't need
1819 /// to initiate your own requests. If you need to send requests to the other side,
1820 /// use [`connect_with`](Self::connect_with) instead.
1821 ///
1822 /// # Example: Byte Stream Transport
1823 ///
1824 /// ```no_run
1825 /// # use agent_client_protocol::UntypedRole;
1826 /// # use agent_client_protocol::{Builder};
1827 /// # use agent_client_protocol_test::*;
1828 /// # async fn example(transport: impl agent_client_protocol::ConnectTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
1829 ///
1830 /// UntypedRole.builder()
1831 /// .on_receive_request(async |req: MyRequest, responder, cx| {
1832 /// responder.respond(MyResponse { status: "ok".into() })
1833 /// }, agent_client_protocol::on_receive_request!())
1834 /// .connect_to(transport)
1835 /// .await?;
1836 /// # Ok(())
1837 /// # }
1838 /// ```
1839 pub async fn connect_to(
1840 self,
1841 transport: impl ConnectTo<Host> + 'static,
1842 ) -> Result<(), crate::Error> {
1843 let (_, future) = self.into_connection_and_future(transport, true, async move |cx| {
1844 cx.incoming_closed().await;
1845 Ok(())
1846 });
1847 future.await
1848 }
1849
1850 /// Run the connection until the provided closure completes.
1851 ///
1852 /// This drives the connection by:
1853 /// 1. Running your registered handlers in the background to process incoming messages
1854 /// 2. Executing your `main_fn` closure with the builder-selected connection
1855 /// context for sending requests and notifications
1856 ///
1857 /// The connection stays active until your `main_fn` returns, then shuts down.
1858 /// Clean incoming EOF fails every pending request and makes future
1859 /// requests fail immediately. It does not cancel unrelated work in
1860 /// `main_fn`: that future may observe the context's `incoming_closed`
1861 /// future, or the builder can use [`on_close`](Self::on_close) to notify it
1862 /// or return an error and stop it.
1863 ///
1864 /// Use this mode when you need to initiate communication (send requests/notifications)
1865 /// in addition to responding to incoming messages. For server-only mode where you just
1866 /// respond to messages, use [`connect_to`](Self::connect_to) instead.
1867 ///
1868 /// # Example
1869 ///
1870 /// ```no_run
1871 /// # use agent_client_protocol::UntypedRole;
1872 /// # use agent_client_protocol::{Builder};
1873 /// # use agent_client_protocol::ByteStreams;
1874 /// # use agent_client_protocol::schema::v1::InitializeRequest;
1875 /// # use agent_client_protocol_test::*;
1876 /// # async fn example(transport: impl agent_client_protocol::ConnectTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
1877 ///
1878 /// UntypedRole.builder()
1879 /// .on_receive_request(async |req: MyRequest, responder, cx| {
1880 /// // Handle incoming requests in the background
1881 /// responder.respond(MyResponse { status: "ok".into() })
1882 /// }, agent_client_protocol::on_receive_request!())
1883 /// .connect_with(transport, async |cx| {
1884 /// // Initialize the protocol
1885 /// let init_response = cx.send_request(InitializeRequest::make())
1886 /// .block_task()
1887 /// .await?;
1888 ///
1889 /// // Send more requests...
1890 /// let result = cx.send_request(MyRequest {})
1891 /// .block_task()
1892 /// .await?;
1893 ///
1894 /// // When this closure returns, the connection shuts down
1895 /// Ok(())
1896 /// })
1897 /// .await?;
1898 /// # Ok(())
1899 /// # }
1900 /// ```
1901 ///
1902 /// # Parameters
1903 ///
1904 /// - `main_fn`: Your client logic. Receives the builder-selected connection
1905 /// context for sending messages.
1906 ///
1907 /// # Errors
1908 ///
1909 /// Returns an error if a handler, background task, transport, or close
1910 /// callback fails, or if `main_fn` returns an error. Clean incoming EOF is
1911 /// observable through the context's `incoming_closed` future and is not
1912 /// itself an error in this mode.
1913 pub async fn connect_with<R>(
1914 self,
1915 transport: impl ConnectTo<Host> + 'static,
1916 main_fn: impl AsyncFnOnce(Context::Connection<Host::Counterpart>) -> Result<R, crate::Error>,
1917 ) -> Result<R, crate::Error> {
1918 let (_, future) =
1919 self.into_connection_and_future(transport, false, async move |connection| {
1920 main_fn(connection_context::from_raw::<Context, _>(connection)).await
1921 });
1922 future.await
1923 }
1924
1925 /// Helper that returns a [`ConnectionTo<R>`] and a future that runs this connection until `main_fn` returns.
1926 fn into_connection_and_future<R>(
1927 self,
1928 transport: impl ConnectTo<Host> + 'static,
1929 wait_owned_transport: bool,
1930 main_fn: impl AsyncFnOnce(ConnectionTo<Host::Counterpart>) -> Result<R, crate::Error>,
1931 ) -> (
1932 ConnectionTo<Host::Counterpart>,
1933 impl Future<Output = Result<R, crate::Error>>,
1934 ) {
1935 let Self {
1936 name,
1937 handler,
1938 runner,
1939 host: me,
1940 protocol_mode,
1941 on_close,
1942 context: _,
1943 } = self;
1944
1945 let (outgoing_tx, outgoing_rx) = mpsc::unbounded();
1946 let (new_task_tx, new_task_rx) = mpsc::unbounded();
1947 let (dynamic_handler_tx, dynamic_handler_rx) = mpsc::unbounded();
1948 let (foreground_succeeded_tx, foreground_succeeded) = completion_signal();
1949 let (foreground_done_tx, foreground_done) = completion_signal();
1950 let pending_replies = PendingReplies::default();
1951 let termination_sources = TerminationSources::default();
1952
1953 // Normalize the transport without losing ownership or finish metadata.
1954 let transport_component = crate::DynConnectTo::new(transport);
1955 let (transport_channel, mut transport_future) =
1956 transport_component.into_channel_and_future();
1957 let owned_transport = transport_future.is_some();
1958 let transport_finish = transport_future
1959 .as_mut()
1960 .and_then(crate::ConnectionDriver::take_finish);
1961 let (transport_completion_tx, transport_completion_rx) = oneshot::channel();
1962 let transport_completion = transport_completion_rx
1963 .map(|result| {
1964 result.unwrap_or_else(|error| {
1965 Err(crate::util::internal_error(format!(
1966 "transport task dropped before reporting completion: {error}"
1967 )))
1968 })
1969 })
1970 .boxed()
1971 .shared();
1972
1973 let connection = ConnectionTo::new(
1974 me.counterpart(),
1975 outgoing_tx,
1976 new_task_tx,
1977 dynamic_handler_tx,
1978 transport_completion,
1979 pending_replies.registrar(),
1980 protocol_mode,
1981 );
1982 // Transport progress must outlive successful foreground completion.
1983 // Application tasks remain cancellable. The inherited close-callback
1984 // phase still polls them for cleanup, but physical drain alone does not.
1985 let transport_driver = if let Some(driver) = transport_future {
1986 let termination_sources = termination_sources.clone();
1987 async move {
1988 let result = driver.await;
1989 termination_sources.record(TerminationSource::Transport, &result);
1990 drop(transport_completion_tx.send(result.clone()));
1991 result
1992 }
1993 .boxed()
1994 } else {
1995 // Channel-only endpoints have no physical sink work to await.
1996 // Their protocol drain marker still orders accepted output.
1997 drop(transport_completion_tx.send(Ok(())));
1998 future::ready(Ok(())).boxed()
1999 };
2000
2001 // Destructure the channel endpoints
2002 let Channel {
2003 rx: mut transport_incoming_rx,
2004 tx: transport_outgoing_tx,
2005 } = transport_channel;
2006
2007 let transport_incoming = futures::stream::poll_fn({
2008 let mut completion = connection.transport_completion.clone();
2009 let mut completed = false;
2010 move |cx| {
2011 if owned_transport
2012 && !completed
2013 && let std::task::Poll::Ready(Ok(())) =
2014 std::pin::Pin::new(&mut completion).poll(cx)
2015 {
2016 // Owned completion closes the producer boundary, not the
2017 // accepted buffer. The incoming actor still dispatches every
2018 // accepted frame and completes its close callbacks in order.
2019 transport_incoming_rx.close();
2020 completed = true;
2021 }
2022 transport_incoming_rx.poll_next_unpin(cx)
2023 }
2024 });
2025
2026 let protocol_compat = ProtocolCompat::new(protocol_mode);
2027
2028 // Constructed outside the driver so dropping it unpolled still
2029 // publishes a termination.
2030 let termination_guard = TerminationGuard {
2031 incoming_closed: connection.incoming_closed.clone(),
2032 pending_replies: pending_replies.clone(),
2033 };
2034
2035 let future = crate::util::instrument_with_connection_name(name, {
2036 let connection = connection.clone();
2037 async move {
2038 let sources = &termination_sources;
2039 let driver = async {
2040 let background = async {
2041 let incoming = {
2042 let pending_replies = pending_replies.clone();
2043 let protocol_compat = protocol_compat.clone();
2044 async {
2045 let mut transport_incoming = std::pin::pin!(transport_incoming);
2046 let incoming = incoming_actor::incoming_protocol_actor(
2047 me.counterpart(),
2048 &connection,
2049 transport_incoming.as_mut(),
2050 dynamic_handler_rx,
2051 pending_replies,
2052 incoming_actor::IncomingHandlers::new(
2053 handler,
2054 on_close,
2055 foreground_succeeded.clone(),
2056 ),
2057 protocol_compat,
2058 );
2059 // Success stops delivery, not physical I/O. An
2060 // underway close callback finishes before sealing.
2061 let result = run_incoming_until_foreground_succeeds(
2062 incoming,
2063 foreground_succeeded,
2064 connection.incoming_closed.clone(),
2065 )
2066 .await;
2067 sources.record(TerminationSource::Handler, &result);
2068 if result.is_err() {
2069 connection.request_shutdown();
2070 }
2071 result?;
2072 // Keep the raw producer boundary alive while the
2073 // physical driver drains. Discard without application
2074 // delivery, rather than fail a late read-side send.
2075 while transport_incoming.next().await.is_some() {}
2076 Ok(())
2077 }
2078 };
2079 let other_actors = async {
2080 let result = futures::try_join!(
2081 // A ready driver error is authoritative even if its
2082 // closed channel would also make output forwarding fail.
2083 transport_driver,
2084 // Protocol layer: OutgoingMessage -> RawJsonRpcMessage
2085 outgoing_actor::outgoing_protocol_actor(
2086 outgoing_rx,
2087 pending_replies,
2088 transport_outgoing_tx,
2089 protocol_compat,
2090 foreground_done,
2091 )
2092 .inspect(
2093 |result| sources.record(TerminationSource::Transport, result)
2094 ),
2095 );
2096 // Signal before awaiting an underway close callback.
2097 if result.is_err() {
2098 connection.request_shutdown();
2099 }
2100 result?;
2101 Ok(())
2102 };
2103
2104 // Keep close callbacks alive when another core actor fails.
2105 // The outer coordination provides the same protection when
2106 // EOF wakes an application task or the foreground.
2107 run_until_connection_close(
2108 incoming,
2109 other_actors,
2110 connection.incoming_closed.clone(),
2111 )
2112 .await
2113 };
2114
2115 run_until_connection_close(
2116 finish_actor_error(background, &connection),
2117 async {
2118 let application = async {
2119 futures::try_join!(
2120 finish_actor_error(
2121 task_actor::task_actor(new_task_rx, &connection)
2122 .inspect(|result| sources
2123 .record(TerminationSource::Handler, result),),
2124 &connection,
2125 ),
2126 finish_actor_error(
2127 runner
2128 .run_with_connection_to(connection.clone())
2129 .inspect(|result| sources
2130 .record(TerminationSource::Handler, result),),
2131 &connection,
2132 ),
2133 )?;
2134 Ok(())
2135 };
2136 let result = run_until_connection_close(
2137 application,
2138 async {
2139 let result = main_fn(connection.clone()).await;
2140 sources.record(TerminationSource::Foreground, &result);
2141 connection.request_shutdown();
2142 if result.is_ok() {
2143 // Stop new incoming delivery immediately,
2144 // including during the callback cleanup phase.
2145 let _ = foreground_succeeded_tx.send(());
2146 }
2147 // Shutdown cancels local consumers, not remote
2148 // requests. Do not add cancellation traffic while
2149 // dropping those consumers before the drain.
2150 connection.pending_replies.disarm_cancellations();
2151 // The application actor (including actual scoped
2152 // runners) remains polled while supervisors finish.
2153 // Only then may ordinary application tasks drop.
2154 connection.wait_protected_operations().await;
2155 result
2156 },
2157 connection.incoming_closed.clone(),
2158 )
2159 .await?;
2160 let _ = foreground_done_tx.send(());
2161 let drained = connection
2162 .drain_outgoing(transport_finish, wait_owned_transport)
2163 .await;
2164 sources.record(TerminationSource::Transport, &drained);
2165 drained?;
2166 Ok(result)
2167 },
2168 connection.incoming_closed.clone(),
2169 )
2170 .await
2171 };
2172 // Every actor, handler, and task has completed or been
2173 // dropped by the time `driver` resolves.
2174 let result = driver.await;
2175 termination_guard.finish(match &result {
2176 // A foreground that finishes after observing EOF still
2177 // ended at a clean EOF boundary.
2178 Ok(_) if connection.incoming_closed.is_closing() => {
2179 ConnectionTermination::IncomingClosed
2180 }
2181 Ok(_) => ConnectionTermination::LocalShutdown,
2182 Err(error) => sources.classify(error.clone()),
2183 });
2184 result
2185 }
2186 });
2187
2188 (connection, future)
2189 }
2190}
2191
2192/// Defer an actor error without dropping the other actors that drive owned
2193/// cleanup or an underway close callback. Physical output drain stays separate.
2194async fn finish_actor_error<R: Role>(
2195 actor: impl Future<Output = Result<(), crate::Error>>,
2196 connection: &ConnectionTo<R>,
2197) -> Result<(), crate::Error> {
2198 let result = actor.await;
2199 if result.is_err() {
2200 connection.request_shutdown();
2201 if connection.incoming_closed.is_closing() {
2202 connection.incoming_closed.closed().await;
2203 }
2204 connection.wait_protected_operations().await;
2205 }
2206 result
2207}
2208
2209#[cfg(feature = "unstable_mcp_over_acp")]
2210impl<
2211 Host: Role,
2212 Handler: HandleDispatchFrom<Host::Counterpart>,
2213 Runner: RunWithConnectionTo<Host::Counterpart>,
2214 Close: HandleConnectionClose<Host::Counterpart>,
2215> Builder<Host, Handler, Runner, Close, RawConnectionContext>
2216{
2217 /// Add an MCP server to protocol v1 session setup requests proxied through
2218 /// this connection.
2219 ///
2220 /// The same native MCP server declaration is added to new, load, and resume
2221 /// requests, plus fork requests when `unstable_session_fork` is enabled.
2222 ///
2223 /// Only applicable to proxies. Use the same method on `V2Builder` to
2224 /// attach the server to protocol v2 setup requests.
2225 pub fn with_mcp_server(
2226 self,
2227 mcp_server: McpServer<Host::Counterpart, impl RunWithConnectionTo<Host::Counterpart>>,
2228 ) -> Builder<
2229 Host,
2230 impl HandleDispatchFrom<Host::Counterpart>,
2231 impl RunWithConnectionTo<Host::Counterpart>,
2232 Close,
2233 RawConnectionContext,
2234 >
2235 where
2236 Host::Counterpart: HasPeer<Agent> + HasPeer<Client>,
2237 {
2238 let (handler, runner) = mcp_server.into_handler_and_runner();
2239 self.with_handler(handler).with_runner(runner)
2240 }
2241}
2242
2243#[cfg(all(feature = "unstable_mcp_over_acp", feature = "unstable_protocol_v2"))]
2244impl<
2245 Host: Role,
2246 Handler: HandleDispatchFrom<Host::Counterpart>,
2247 Runner: RunWithConnectionTo<Host::Counterpart>,
2248 Close: HandleConnectionClose<Host::Counterpart>,
2249> Builder<Host, Handler, Runner, Close, V2ConnectionContext>
2250{
2251 /// Add an MCP server to protocol v2 session setup requests proxied through
2252 /// this connection.
2253 ///
2254 /// The same native MCP server declaration is added to new and resume
2255 /// requests, plus fork requests when `unstable_session_fork` is enabled.
2256 /// Unrelated request fields are preserved exactly.
2257 ///
2258 /// Only applicable to proxies.
2259 pub fn with_mcp_server(
2260 self,
2261 mcp_server: McpServer<Host::Counterpart, impl RunWithConnectionTo<Host::Counterpart>>,
2262 ) -> Builder<
2263 Host,
2264 impl HandleDispatchFrom<Host::Counterpart>,
2265 impl RunWithConnectionTo<Host::Counterpart>,
2266 Close,
2267 V2ConnectionContext,
2268 >
2269 where
2270 Host::Counterpart: HasPeer<Agent> + HasPeer<Client>,
2271 {
2272 let (handler, runner) = mcp_server.into_v2_handler_and_runner();
2273 self.with_handler(handler).with_runner(runner)
2274 }
2275}
2276
2277impl<R, H, Run, Close, Context> ConnectTo<R::Counterpart> for Builder<R, H, Run, Close, Context>
2278where
2279 R: Role,
2280 H: HandleDispatchFrom<R::Counterpart> + 'static,
2281 Run: RunWithConnectionTo<R::Counterpart> + 'static,
2282 Close: HandleConnectionClose<R::Counterpart> + 'static,
2283 Context: ConnectionContext,
2284{
2285 async fn connect_to(self, client: impl ConnectTo<R>) -> Result<(), crate::Error> {
2286 Builder::connect_to(self, client).await
2287 }
2288}
2289
2290/// The payload sent through the response oneshot channel.
2291///
2292/// Includes the response value and an optional ack channel for dispatch loop
2293/// synchronization.
2294pub(crate) struct ResponsePayload {
2295 /// The response result - either the JSON value or an error.
2296 pub(crate) result: Result<serde_json::Value, crate::Error>,
2297
2298 /// Optional acknowledgment channel for dispatch loop synchronization.
2299 ///
2300 /// When present, the receiver must send on this channel to signal that
2301 /// response processing is complete, allowing the dispatch loop to continue
2302 /// to the next message.
2303 ///
2304 /// This is present when ordered response consumption was selected before
2305 /// the response was routed during its original dispatch. Public callback
2306 /// consumption and framework-owned ordered blocking transforms can hold
2307 /// the dispatch loop; ordinary blocking consumers, local error paths, and
2308 /// responses routed later do not.
2309 pub(crate) ack_tx: Option<oneshot::Sender<()>>,
2310}
2311
2312type ResponseRouteHook =
2313 Box<dyn FnOnce(&str, &serde_json::Value) -> Result<(), crate::Error> + Send>;
2314
2315/// A prerequisite that must complete before an outgoing request is published
2316/// to the transport.
2317struct RequestReadiness {
2318 future: BoxFuture<'static, Result<(), crate::Error>>,
2319}
2320
2321impl RequestReadiness {
2322 fn new(future: impl Future<Output = Result<(), crate::Error>> + Send + 'static) -> Self {
2323 Self {
2324 future: future.boxed(),
2325 }
2326 }
2327}
2328
2329impl Future for RequestReadiness {
2330 type Output = Result<(), crate::Error>;
2331
2332 fn poll(
2333 mut self: std::pin::Pin<&mut Self>,
2334 cx: &mut std::task::Context<'_>,
2335 ) -> std::task::Poll<Self::Output> {
2336 self.future.as_mut().poll(cx)
2337 }
2338}
2339
2340impl Debug for RequestReadiness {
2341 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2342 formatter
2343 .debug_struct("RequestReadiness")
2344 .finish_non_exhaustive()
2345 }
2346}
2347
2348impl std::fmt::Debug for ResponsePayload {
2349 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2350 f.debug_struct("ResponsePayload")
2351 .field("result", &self.result)
2352 .field("ack_tx", &self.ack_tx.as_ref().map(|_| "..."))
2353 .finish()
2354 }
2355}
2356
2357#[derive(Clone, Debug, Default)]
2358struct ResponseOrdering {
2359 ordered: Arc<AtomicBool>,
2360}
2361
2362impl ResponseOrdering {
2363 fn mark_ordered(&self) {
2364 self.ordered.store(true, Ordering::Release);
2365 }
2366
2367 fn is_ordered(&self) -> bool {
2368 self.ordered.load(Ordering::Acquire)
2369 }
2370}
2371
2372struct PendingReply {
2373 method: String,
2374 role_id: RoleId,
2375 sender: oneshot::Sender<ResponsePayload>,
2376 cancellation_disarm: SentRequestCancellationDisarm,
2377 ordering: ResponseOrdering,
2378 response_route_hook: Option<ResponseRouteHook>,
2379 /// Set only by `V2ConnectionTo::send_initialize`, whose typed outcome
2380 /// reports a lower-version initialize response as a success.
2381 accepts_lower_initialize_version: bool,
2382}
2383
2384impl PendingReply {
2385 fn fail(self, error: crate::Error) {
2386 self.cancellation_disarm.disarm();
2387 if self
2388 .sender
2389 .send(ResponsePayload {
2390 result: Err(error),
2391 ack_tx: None,
2392 })
2393 .is_err()
2394 {
2395 tracing::trace!(method = %self.method, "Pending request was already dropped");
2396 }
2397 }
2398
2399 fn fail_incoming_closed(self) {
2400 let error = incoming_transport_closed_error(&self.method);
2401 self.fail(error);
2402 }
2403}
2404
2405#[derive(Default)]
2406struct PendingRepliesInner {
2407 incoming_closed: bool,
2408 termination: Option<ConnectionTermination>,
2409 replies: HashMap<RequestId, PendingReply>,
2410}
2411
2412#[derive(Clone, Default)]
2413struct PendingReplies {
2414 inner: Arc<Mutex<PendingRepliesInner>>,
2415}
2416
2417impl PendingReplies {
2418 fn registrar(&self) -> PendingRepliesRegistrar {
2419 PendingRepliesRegistrar {
2420 inner: Arc::downgrade(&self.inner),
2421 }
2422 }
2423
2424 fn contains(&self, id: &RequestId) -> bool {
2425 self.inner
2426 .lock()
2427 .expect("pending replies mutex poisoned")
2428 .replies
2429 .contains_key(id)
2430 }
2431
2432 fn remove(&self, id: &RequestId) -> Option<PendingReply> {
2433 self.inner
2434 .lock()
2435 .expect("pending replies mutex poisoned")
2436 .replies
2437 .remove(id)
2438 }
2439
2440 /// Atomically reject new subscriptions and fail every existing one.
2441 fn close_incoming(&self) -> usize {
2442 let replies = {
2443 let mut inner = self.inner.lock().expect("pending replies mutex poisoned");
2444 inner.incoming_closed = true;
2445 std::mem::take(&mut inner.replies)
2446 };
2447 let count = replies.len();
2448 for (_, reply) in replies {
2449 reply.fail_incoming_closed();
2450 }
2451 count
2452 }
2453
2454 /// Atomically reject new subscriptions and fail every remaining one
2455 /// because the driver has terminated.
2456 fn terminate(&self, termination: &ConnectionTermination) -> usize {
2457 let replies = {
2458 let mut inner = self.inner.lock().expect("pending replies mutex poisoned");
2459 inner.termination = Some(termination.clone());
2460 std::mem::take(&mut inner.replies)
2461 };
2462 let count = replies.len();
2463 for (_, reply) in replies {
2464 let error = connection_terminated_error(&reply.method, termination);
2465 reply.fail(error);
2466 }
2467 count
2468 }
2469}
2470
2471/// A non-owning handle used to register a request before it enters the
2472/// outgoing queue. Keeping this weak prevents escaped [`ConnectionTo`] clones
2473/// from extending the lifetime of response senders after the driver stops.
2474#[derive(Clone)]
2475struct PendingRepliesRegistrar {
2476 inner: Weak<Mutex<PendingRepliesInner>>,
2477}
2478
2479impl PendingRepliesRegistrar {
2480 fn disarm_cancellations(&self) {
2481 if let Some(inner) = self.inner.upgrade() {
2482 let inner = inner.lock().expect("pending replies mutex poisoned");
2483 for reply in inner.replies.values() {
2484 reply.cancellation_disarm.disarm();
2485 }
2486 }
2487 }
2488
2489 /// Register a response destination before the request becomes observable.
2490 ///
2491 /// Returns an error after failing `reply` when EOF has already made a
2492 /// response impossible or the connection driver is no longer running.
2493 fn subscribe(
2494 &self,
2495 id: RequestId,
2496 reply: PendingReply,
2497 incoming_closed: &IncomingClosed,
2498 ) -> Result<(), crate::Error> {
2499 let Some(inner) = self.inner.upgrade() else {
2500 let error = if incoming_closed.is_closing() {
2501 incoming_transport_closed_error(&reply.method)
2502 } else if let Some(termination) = incoming_closed.termination() {
2503 connection_terminated_error(&reply.method, &termination)
2504 } else {
2505 crate::util::internal_error(format!(
2506 "failed to send outgoing request `{}`: connection is no longer running",
2507 reply.method
2508 ))
2509 };
2510 reply.fail(error.clone());
2511 return Err(error);
2512 };
2513
2514 let result = {
2515 let mut inner = inner.lock().expect("pending replies mutex poisoned");
2516 if inner.incoming_closed {
2517 Err((reply, None))
2518 } else if let Some(termination) = &inner.termination {
2519 Err((reply, Some(termination.clone())))
2520 } else {
2521 Ok(inner.replies.insert(id, reply))
2522 }
2523 };
2524
2525 match result {
2526 Err((rejected, termination)) => {
2527 let error = match termination {
2528 Some(termination) => {
2529 connection_terminated_error(&rejected.method, &termination)
2530 }
2531 None => incoming_transport_closed_error(&rejected.method),
2532 };
2533 rejected.fail(error.clone());
2534 Err(error)
2535 }
2536 Ok(replaced) => {
2537 if let Some(replaced) = replaced {
2538 replaced.fail(
2539 crate::Error::internal_error()
2540 .data("outgoing request ID was reused before its response arrived"),
2541 );
2542 }
2543 Ok(())
2544 }
2545 }
2546 }
2547
2548 fn remove(&self, id: &RequestId) -> Option<PendingReply> {
2549 self.inner
2550 .upgrade()?
2551 .lock()
2552 .expect("pending replies mutex poisoned")
2553 .replies
2554 .remove(id)
2555 }
2556}
2557
2558impl Debug for PendingRepliesRegistrar {
2559 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2560 formatter
2561 .debug_struct("PendingRepliesRegistrar")
2562 .field("is_connected", &(self.inner.strong_count() > 0))
2563 .finish()
2564 }
2565}
2566
2567/// A request-local marker that is set when the peer asks to cancel the request.
2568///
2569/// Request handlers can get this handle from [`Responder::cancellation`] and
2570/// use it from spawned work to stop long-running request processing
2571/// cooperatively.
2572#[derive(Clone)]
2573pub struct RequestCancellation {
2574 state: Arc<RequestCancellationState>,
2575}
2576
2577struct RequestCancellationState {
2578 cancelled: AtomicBool,
2579 signal_tx: Mutex<Option<oneshot::Sender<()>>>,
2580 signal_rx: future::Shared<BoxFuture<'static, ()>>,
2581}
2582
2583impl RequestCancellation {
2584 fn new() -> Self {
2585 let (signal_tx, signal_rx) = oneshot::channel();
2586 let signal_rx = signal_rx.map(|_| ()).boxed().shared();
2587 Self {
2588 state: Arc::new(RequestCancellationState {
2589 cancelled: AtomicBool::new(false),
2590 signal_tx: Mutex::new(Some(signal_tx)),
2591 signal_rx,
2592 }),
2593 }
2594 }
2595
2596 /// Wait until the peer sends `$/cancel_request` for this request.
2597 ///
2598 /// If cancellation was already requested, this returns immediately.
2599 pub async fn cancelled(&self) {
2600 self.state.signal_rx.clone().await;
2601 }
2602
2603 /// Run request work until it completes or the peer asks to cancel it.
2604 ///
2605 /// If cancellation is requested first, this returns
2606 /// [`Error::request_cancelled`]. This is a convenience for request handlers
2607 /// that want to respond with the normal result or the standard
2608 /// cancellation error.
2609 ///
2610 /// When cancellation wins, `future` is dropped: work stops at its next
2611 /// await point, partial results are lost, and any cleanup must happen in
2612 /// `Drop` implementations. Handlers that need to flush partial results or
2613 /// run async cleanup should instead watch [`cancelled`](Self::cancelled)
2614 /// or poll [`is_cancelled`](Self::is_cancelled) from inside the work.
2615 ///
2616 /// [`Error::request_cancelled`]: crate::Error::request_cancelled
2617 pub async fn run_until_cancelled<T>(
2618 &self,
2619 future: impl std::future::Future<Output = Result<T, crate::Error>>,
2620 ) -> Result<T, crate::Error> {
2621 if self.is_cancelled() {
2622 return Err(crate::Error::request_cancelled());
2623 }
2624
2625 match future::select(pin!(future), pin!(self.cancelled())).await {
2626 Either::Left((result, _)) => result,
2627 Either::Right(((), _)) => Err(crate::Error::request_cancelled()),
2628 }
2629 }
2630
2631 /// Returns whether the peer has already requested cancellation.
2632 #[must_use]
2633 pub fn is_cancelled(&self) -> bool {
2634 self.state.cancelled.load(Ordering::Acquire)
2635 }
2636
2637 fn cancel(&self) {
2638 if self.state.cancelled.swap(true, Ordering::AcqRel) {
2639 return;
2640 }
2641
2642 let signal_tx = self
2643 .state
2644 .signal_tx
2645 .lock()
2646 .expect("request cancellation signal mutex poisoned")
2647 .take();
2648
2649 // Complete the oneshot outside the lock: it wakes waiters, and
2650 // arbitrary waker code must not observe the lock held.
2651 if let Some(signal_tx) = signal_tx {
2652 let _ = signal_tx.send(());
2653 }
2654 }
2655}
2656
2657impl Debug for RequestCancellation {
2658 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2659 formatter
2660 .debug_struct("RequestCancellation")
2661 .field("is_cancelled", &self.is_cancelled())
2662 .finish_non_exhaustive()
2663 }
2664}
2665
2666/// Per-request cancellation state tracked by [`RequestCancellationRegistry`].
2667///
2668/// The full [`RequestCancellation`] marker (with its wakeup machinery) is only
2669/// allocated once a handler asks for it via [`Responder::cancellation`]; until
2670/// then an incoming `$/cancel_request` just flips the entry to `Cancelled`.
2671/// This keeps the per-request cost of the registry to a single map entry.
2672#[derive(Debug)]
2673enum RequestCancellationEntry {
2674 /// The request is in flight; no marker handed out, no cancellation yet.
2675 Armed,
2676 /// `$/cancel_request` arrived before a marker was handed out.
2677 Cancelled,
2678 /// A marker was handed out via [`Responder::cancellation`].
2679 Marker(RequestCancellation),
2680}
2681
2682/// A registered request's cancellation state, tagged with the generation of
2683/// its registration.
2684///
2685/// The generation distinguishes a registration from earlier ones that used
2686/// the same request ID, so that when a (protocol-violating) peer reuses the
2687/// ID of a request that is still in flight, the stale request's responder can
2688/// neither remove nor observe the cancellation state of the newer request.
2689#[derive(Debug)]
2690struct RequestCancellationSlot {
2691 generation: u64,
2692 entry: RequestCancellationEntry,
2693}
2694
2695#[derive(Debug, Default)]
2696struct RequestCancellationRegistryInner {
2697 slots: HashMap<RequestId, RequestCancellationSlot>,
2698 next_generation: u64,
2699}
2700
2701#[derive(Clone, Debug, Default)]
2702struct RequestCancellationRegistry {
2703 inner: Arc<Mutex<RequestCancellationRegistryInner>>,
2704}
2705
2706#[derive(Debug)]
2707struct ResponderCancellation {
2708 id: RequestId,
2709 generation: u64,
2710 registry: RequestCancellationRegistry,
2711}
2712
2713impl RequestCancellationRegistry {
2714 fn new() -> Self {
2715 Self::default()
2716 }
2717
2718 fn register(&self, id: &RequestId) -> ResponderCancellation {
2719 let generation = {
2720 let mut inner = self
2721 .inner
2722 .lock()
2723 .expect("request cancellation registry mutex poisoned");
2724 let generation = inner.next_generation;
2725 inner.next_generation += 1;
2726 if inner
2727 .slots
2728 .insert(
2729 id.clone(),
2730 RequestCancellationSlot {
2731 generation,
2732 entry: RequestCancellationEntry::Armed,
2733 },
2734 )
2735 .is_some()
2736 {
2737 tracing::debug!(
2738 ?id,
2739 "peer reused the ID of a request that is still in flight"
2740 );
2741 }
2742 generation
2743 };
2744 ResponderCancellation {
2745 id: id.clone(),
2746 generation,
2747 registry: self.clone(),
2748 }
2749 }
2750
2751 /// Get the cancellation marker for a registered request, creating it on
2752 /// first use. Repeated calls return markers that share the same state.
2753 ///
2754 /// Exception: when the registration is stale (a protocol-violating peer
2755 /// reused this request ID and the slot now belongs to a newer request, or
2756 /// was already removed by it), every call returns a fresh *detached*
2757 /// marker. Detached markers can never fire, and detached markers from
2758 /// repeated calls do not share state with each other.
2759 fn marker(&self, id: &RequestId, generation: u64) -> RequestCancellation {
2760 let mut inner = self
2761 .inner
2762 .lock()
2763 .expect("request cancellation registry mutex poisoned");
2764 let Some(slot) = inner.slots.get_mut(id) else {
2765 // The slot lives as long as the responder that owns it, so this
2766 // is only reachable if the peer reused this request ID and the
2767 // newer request's responder already removed the replacement slot.
2768 // Hand out a detached marker rather than panicking.
2769 return RequestCancellation::new();
2770 };
2771 if slot.generation != generation {
2772 // The peer reused this request ID while the request was still in
2773 // flight, and the slot now belongs to the newer request. Hand the
2774 // stale responder a detached marker instead of cross-wiring the
2775 // two requests' cancellation states.
2776 return RequestCancellation::new();
2777 }
2778 let entry = &mut slot.entry;
2779 match entry {
2780 RequestCancellationEntry::Marker(marker) => marker.clone(),
2781 RequestCancellationEntry::Armed => {
2782 let marker = RequestCancellation::new();
2783 *entry = RequestCancellationEntry::Marker(marker.clone());
2784 marker
2785 }
2786 RequestCancellationEntry::Cancelled => {
2787 // No one can be waiting on a marker that did not exist yet,
2788 // so firing it while holding the registry lock is fine.
2789 let marker = RequestCancellation::new();
2790 marker.cancel();
2791 *entry = RequestCancellationEntry::Marker(marker.clone());
2792 marker
2793 }
2794 }
2795 }
2796
2797 fn cancel_if_requested(&self, dispatch: &Dispatch) -> Result<bool, crate::Error> {
2798 let Some(request_id) = cancellation_request_id(dispatch)? else {
2799 return Ok(false);
2800 };
2801 Ok(self.cancel(&request_id))
2802 }
2803
2804 /// Mark whichever request currently owns `request_id` as cancelled.
2805 fn cancel(&self, request_id: &RequestId) -> bool {
2806 let marker = {
2807 let mut inner = self
2808 .inner
2809 .lock()
2810 .expect("request cancellation registry mutex poisoned");
2811 let Some(slot) = inner.slots.get_mut(request_id) else {
2812 return false;
2813 };
2814 let entry = &mut slot.entry;
2815 match entry {
2816 RequestCancellationEntry::Marker(marker) => marker.clone(),
2817 RequestCancellationEntry::Cancelled => return true,
2818 RequestCancellationEntry::Armed => {
2819 *entry = RequestCancellationEntry::Cancelled;
2820 return true;
2821 }
2822 }
2823 };
2824
2825 // Fire the marker outside the registry lock: waking waiters runs
2826 // arbitrary waker code that must not observe the lock held.
2827 marker.cancel();
2828 true
2829 }
2830
2831 /// Remove the slot for `request_id`, but only if it still belongs to the
2832 /// registration identified by `generation`.
2833 fn remove(&self, request_id: &RequestId, generation: u64) {
2834 let mut inner = self
2835 .inner
2836 .lock()
2837 .expect("request cancellation registry mutex poisoned");
2838 if inner
2839 .slots
2840 .get(request_id)
2841 .is_some_and(|slot| slot.generation == generation)
2842 {
2843 inner.slots.remove(request_id);
2844 }
2845 }
2846}
2847
2848impl ResponderCancellation {
2849 fn cancellation(&self) -> RequestCancellation {
2850 self.registry.marker(&self.id, self.generation)
2851 }
2852}
2853
2854impl Drop for ResponderCancellation {
2855 fn drop(&mut self) {
2856 self.registry.remove(&self.id, self.generation);
2857 }
2858}
2859
2860fn cancellation_request_id(dispatch: &Dispatch) -> Result<Option<RequestId>, crate::Error> {
2861 let Dispatch::Notification(message) = dispatch else {
2862 return Ok(None);
2863 };
2864 cancellation_request_id_from_message(message)
2865}
2866
2867fn cancellation_request_id_from_message(
2868 message: &UntypedMessage,
2869) -> Result<Option<RequestId>, crate::Error> {
2870 let (method, params) = peel_successor_envelopes(&message.method, &message.params);
2871 if !crate::schema::v1::CancelRequestNotification::matches_method(method) {
2872 return Ok(None);
2873 }
2874
2875 let notification = crate::schema::v1::CancelRequestNotification::parse_message(method, params)?;
2876 Ok(Some(notification.request_id))
2877}
2878
2879/// Peel any [`SuccessorMessage`] envelopes off a notification by reference,
2880/// returning the innermost method and params.
2881///
2882/// This only peeks at the envelope's `method`/`params` fields instead of
2883/// deserializing the envelope, for two reasons:
2884///
2885/// - It avoids deep-cloning the params of every wrapped notification on the
2886/// hot dispatch path just to inspect the inner method name.
2887/// - It is deliberately lenient: a malformed envelope is left as-is here and
2888/// flows on to the handler chain, which is responsible for reporting it.
2889///
2890/// [`SuccessorMessage`]: crate::schema::SuccessorMessage
2891fn peel_successor_envelopes<'message>(
2892 mut method: &'message str,
2893 mut params: &'message serde_json::Value,
2894) -> (&'message str, &'message serde_json::Value) {
2895 while crate::schema::SuccessorMessage::<UntypedMessage>::matches_method(method) {
2896 let Some(inner_method) = params.get("method").and_then(serde_json::Value::as_str) else {
2897 break;
2898 };
2899 method = inner_method;
2900 params = params.get("params").unwrap_or(&serde_json::Value::Null);
2901 }
2902 (method, params)
2903}
2904
2905/// Whether a notification is a `$/cancel_request`, even when it is still
2906/// wrapped in `_proxy/successor` envelopes.
2907///
2908/// `$/cancel_request` is connection-scoped: its `requestId` was allocated on
2909/// the connection the notification arrived over and means nothing on any
2910/// other connection. Generic forwarding code (such as
2911/// [`ConnectionTo::send_proxied_message_to`]) uses this check to drop the raw
2912/// notification instead of tunneling it across a hop; the cancellation still
2913/// propagates because [`forward_response_to`](SentRequest::forward_response_to)
2914/// re-issues it with the forwarded request's own ID.
2915///
2916/// Checking a notification whose method is not the successor envelope is a
2917/// plain method-name comparison. Only successor-wrapped notifications pay for
2918/// a serialization to peel the envelope.
2919#[must_use]
2920pub fn is_cancel_request_notification<N: JsonRpcNotification>(notification: &N) -> bool {
2921 let method = notification.method();
2922 if crate::schema::v1::CancelRequestNotification::matches_method(method) {
2923 return true;
2924 }
2925 if !crate::schema::SuccessorMessage::<UntypedMessage>::matches_method(method) {
2926 return false;
2927 }
2928
2929 match notification.to_untyped_message() {
2930 Ok(untyped) => {
2931 let (method, _params) = peel_successor_envelopes(&untyped.method, &untyped.params);
2932 crate::schema::v1::CancelRequestNotification::matches_method(method)
2933 }
2934 Err(error) => {
2935 tracing::debug!(
2936 error = ?crate::util::redacted_error(error.code),
2937 "failed to inspect successor-wrapped notification for cancellation"
2938 );
2939 false
2940 }
2941 }
2942}
2943
2944/// Messages send to be serialized over the transport.
2945#[derive(Clone)]
2946enum ResponseDestination {
2947 Individual(IndividualResponseSlot),
2948 Batch(BatchResponseSlot),
2949}
2950
2951impl std::fmt::Debug for ResponseDestination {
2952 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2953 match self {
2954 Self::Individual(slot) => formatter.debug_tuple("Individual").field(slot).finish(),
2955 Self::Batch(slot) => formatter.debug_tuple("Batch").field(slot).finish(),
2956 }
2957 }
2958}
2959
2960impl ResponseDestination {
2961 fn individual() -> Self {
2962 Self::Individual(IndividualResponseSlot::default())
2963 }
2964
2965 fn batch(slot_count: usize) -> (impl Iterator<Item = Self>, BatchDispatchCompletion) {
2966 let state = Arc::new(Mutex::new(BatchResponseState {
2967 remaining: slot_count,
2968 responses: (0..slot_count).map(|_| None).collect(),
2969 abandoned: (0..slot_count).map(|_| None).collect(),
2970 active_handler_attempts: (0..slot_count).map(|_| 0).collect(),
2971 dispatch_complete: false,
2972 emitted: false,
2973 }));
2974
2975 (
2976 (0..slot_count).map({
2977 let state = state.clone();
2978 move |index| {
2979 Self::Batch(BatchResponseSlot {
2980 state: state.clone(),
2981 index,
2982 })
2983 }
2984 }),
2985 BatchDispatchCompletion { state },
2986 )
2987 }
2988
2989 fn complete(self, response: RawJsonRpcMessage) -> Option<TransportFrame> {
2990 match self {
2991 Self::Individual(slot) => slot.complete(response),
2992 Self::Batch(slot) => slot.complete(response).map(batch_response_frame),
2993 }
2994 }
2995
2996 fn abandon(self, fallback: RawJsonRpcMessage) -> Option<TransportFrame> {
2997 match self {
2998 Self::Individual(_) => None,
2999 Self::Batch(slot) => slot.abandon(fallback).map(batch_response_frame),
3000 }
3001 }
3002
3003 fn is_batch(&self) -> bool {
3004 matches!(self, Self::Batch(_))
3005 }
3006
3007 fn begin_handler_attempt(
3008 &self,
3009 message_tx: OutgoingMessageTx,
3010 ) -> Option<ResponderHandlerAttempt> {
3011 let Self::Batch(slot) = self else {
3012 return None;
3013 };
3014 slot.begin_handler_attempt();
3015 Some(ResponderHandlerAttempt {
3016 message_tx,
3017 destination: self.clone(),
3018 })
3019 }
3020
3021 fn finish_handler_attempt(self) -> Option<TransportFrame> {
3022 match self {
3023 Self::Individual(_) => None,
3024 Self::Batch(slot) => slot.finish_handler_attempt().map(batch_response_frame),
3025 }
3026 }
3027}
3028
3029#[derive(Clone, Debug, Default)]
3030struct IndividualResponseSlot {
3031 completed: Arc<AtomicBool>,
3032}
3033
3034impl IndividualResponseSlot {
3035 fn complete(self, response: RawJsonRpcMessage) -> Option<TransportFrame> {
3036 if self.completed.swap(true, Ordering::AcqRel) {
3037 tracing::warn!("Ignoring duplicate completion of JSON-RPC request");
3038 return None;
3039 }
3040
3041 Some(TransportFrame::Single(response))
3042 }
3043}
3044
3045fn batch_response_frame(responses: Vec<RawJsonRpcMessage>) -> TransportFrame {
3046 TransportFrame::Batch(
3047 TransportBatch::from_messages(responses)
3048 .expect("a completed JSON-RPC response batch is non-empty"),
3049 )
3050}
3051
3052#[derive(Clone)]
3053struct BatchDispatchCompletion {
3054 state: Arc<Mutex<BatchResponseState>>,
3055}
3056
3057impl std::fmt::Debug for BatchDispatchCompletion {
3058 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3059 formatter
3060 .debug_struct("BatchDispatchCompletion")
3061 .finish_non_exhaustive()
3062 }
3063}
3064
3065impl BatchDispatchCompletion {
3066 fn complete(self) -> Option<TransportFrame> {
3067 let mut state = self
3068 .state
3069 .lock()
3070 .expect("batch response accumulator mutex poisoned");
3071 if state.dispatch_complete {
3072 tracing::warn!("Ignoring duplicate JSON-RPC batch dispatch completion");
3073 return None;
3074 }
3075 state.dispatch_complete = true;
3076 for index in 0..state.responses.len() {
3077 promote_abandoned_response(&mut state, index);
3078 }
3079 take_completed_batch(&mut state).map(batch_response_frame)
3080 }
3081}
3082
3083fn promote_abandoned_response(state: &mut BatchResponseState, index: usize) {
3084 if state.active_handler_attempts[index] == 0
3085 && state.responses[index].is_none()
3086 && let Some(fallback) = state.abandoned[index].take()
3087 {
3088 state.responses[index] = Some(fallback);
3089 state.remaining -= 1;
3090 }
3091}
3092
3093fn take_completed_batch(state: &mut BatchResponseState) -> Option<Vec<RawJsonRpcMessage>> {
3094 if !state.dispatch_complete || state.remaining != 0 || state.emitted {
3095 return None;
3096 }
3097
3098 state.emitted = true;
3099 Some(
3100 state
3101 .responses
3102 .iter_mut()
3103 .map(|response| {
3104 response
3105 .take()
3106 .expect("completed JSON-RPC batch has every response slot")
3107 })
3108 .collect(),
3109 )
3110}
3111
3112#[derive(Clone)]
3113struct BatchResponseSlot {
3114 state: Arc<Mutex<BatchResponseState>>,
3115 index: usize,
3116}
3117
3118impl std::fmt::Debug for BatchResponseSlot {
3119 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3120 formatter
3121 .debug_struct("BatchResponseSlot")
3122 .field("index", &self.index)
3123 .finish_non_exhaustive()
3124 }
3125}
3126
3127impl BatchResponseSlot {
3128 fn begin_handler_attempt(&self) {
3129 let mut state = self
3130 .state
3131 .lock()
3132 .expect("batch response accumulator mutex poisoned");
3133 state.active_handler_attempts[self.index] += 1;
3134 }
3135
3136 fn finish_handler_attempt(self) -> Option<Vec<RawJsonRpcMessage>> {
3137 let mut state = self
3138 .state
3139 .lock()
3140 .expect("batch response accumulator mutex poisoned");
3141 state.active_handler_attempts[self.index] = state.active_handler_attempts[self.index]
3142 .checked_sub(1)
3143 .expect("handler attempt completion without a matching start");
3144 if state.dispatch_complete {
3145 promote_abandoned_response(&mut state, self.index);
3146 }
3147 take_completed_batch(&mut state)
3148 }
3149
3150 fn complete(self, response: RawJsonRpcMessage) -> Option<Vec<RawJsonRpcMessage>> {
3151 let mut state = self
3152 .state
3153 .lock()
3154 .expect("batch response accumulator mutex poisoned");
3155 if state.emitted {
3156 tracing::warn!(
3157 index = self.index,
3158 "Ignoring response after JSON-RPC batch was already completed"
3159 );
3160 return None;
3161 }
3162 if self.index >= state.responses.len() {
3163 tracing::error!(index = self.index, "Invalid JSON-RPC batch response slot");
3164 return None;
3165 }
3166 if state.responses[self.index].is_some() {
3167 tracing::warn!(
3168 index = self.index,
3169 "Ignoring duplicate completion of JSON-RPC batch response slot"
3170 );
3171 return None;
3172 }
3173
3174 state.abandoned[self.index] = None;
3175 state.responses[self.index] = Some(response);
3176 state.remaining -= 1;
3177 take_completed_batch(&mut state)
3178 }
3179
3180 fn abandon(self, fallback: RawJsonRpcMessage) -> Option<Vec<RawJsonRpcMessage>> {
3181 let mut state = self
3182 .state
3183 .lock()
3184 .expect("batch response accumulator mutex poisoned");
3185 if state.emitted || state.responses[self.index].is_some() {
3186 return None;
3187 }
3188 if state.abandoned[self.index].is_some() {
3189 tracing::warn!(
3190 index = self.index,
3191 "Ignoring duplicate abandonment of JSON-RPC batch response slot"
3192 );
3193 return None;
3194 }
3195
3196 if state.dispatch_complete && state.active_handler_attempts[self.index] == 0 {
3197 state.responses[self.index] = Some(fallback);
3198 state.remaining -= 1;
3199 } else {
3200 state.abandoned[self.index] = Some(fallback);
3201 }
3202 take_completed_batch(&mut state)
3203 }
3204}
3205
3206struct BatchResponseState {
3207 remaining: usize,
3208 responses: Vec<Option<RawJsonRpcMessage>>,
3209 abandoned: Vec<Option<RawJsonRpcMessage>>,
3210 active_handler_attempts: Vec<usize>,
3211 dispatch_complete: bool,
3212 emitted: bool,
3213}
3214
3215#[derive(Clone, Debug)]
3216struct RequestReplyTarget {
3217 id: RequestId,
3218 method: String,
3219 destination: ResponseDestination,
3220}
3221
3222struct ResponderHandlerAttempt {
3223 message_tx: OutgoingMessageTx,
3224 destination: ResponseDestination,
3225}
3226
3227impl Drop for ResponderHandlerAttempt {
3228 fn drop(&mut self) {
3229 if let Err(error) = send_raw_message(
3230 &self.message_tx,
3231 OutgoingMessage::BatchHandlerAttemptComplete {
3232 destination: self.destination.clone(),
3233 },
3234 ) {
3235 tracing::debug!(
3236 error = ?crate::util::redacted_error(error.code),
3237 "could not complete JSON-RPC batch handler attempt"
3238 );
3239 }
3240 }
3241}
3242
3243#[derive(Clone)]
3244struct ResponseReplyTarget {
3245 id: RequestId,
3246 method: String,
3247 sender: Arc<Mutex<Option<oneshot::Sender<ResponsePayload>>>>,
3248 ordering: ResponseOrdering,
3249 dispatch: ResponseDispatch,
3250}
3251
3252impl ResponseReplyTarget {
3253 fn route(self, result: Result<serde_json::Value, crate::Error>) {
3254 let sender = self
3255 .sender
3256 .lock()
3257 .expect("response reply mutex poisoned")
3258 .take();
3259 let Some(sender) = sender else {
3260 tracing::debug!(
3261 method = %self.method,
3262 id = ?self.id,
3263 "response was already routed to its local awaiter"
3264 );
3265 return;
3266 };
3267
3268 let ack_tx = self.dispatch.acknowledgment(&self.ordering);
3269 if sender.send(ResponsePayload { result, ack_tx }).is_err() {
3270 tracing::debug!(
3271 method = %self.method,
3272 id = ?self.id,
3273 "dropped response because local receiver was gone"
3274 );
3275 }
3276 }
3277}
3278
3279#[derive(Clone, Default)]
3280struct ResponseDispatch {
3281 state: Arc<Mutex<ResponseDispatchState>>,
3282}
3283
3284#[derive(Default)]
3285struct ResponseDispatchState {
3286 complete: bool,
3287 ack_rx: Option<oneshot::Receiver<()>>,
3288}
3289
3290impl ResponseDispatch {
3291 fn acknowledgment(&self, ordering: &ResponseOrdering) -> Option<oneshot::Sender<()>> {
3292 if !ordering.is_ordered() {
3293 return None;
3294 }
3295
3296 let mut state = self.state.lock().expect("response dispatch mutex poisoned");
3297 if state.complete {
3298 return None;
3299 }
3300
3301 let (ack_tx, ack_rx) = oneshot::channel();
3302 let previous_ack = state.ack_rx.replace(ack_rx);
3303 debug_assert!(
3304 previous_ack.is_none(),
3305 "a response dispatch can only be routed once"
3306 );
3307 Some(ack_tx)
3308 }
3309
3310 fn complete(&self) -> Option<oneshot::Receiver<()>> {
3311 let mut state = self.state.lock().expect("response dispatch mutex poisoned");
3312 state.complete = true;
3313 state.ack_rx.take()
3314 }
3315}
3316
3317enum HandlerErrorTarget {
3318 Request(RequestReplyTarget),
3319 Response(ResponseReplyTarget),
3320}
3321
3322impl HandlerErrorTarget {
3323 fn begin_handler_attempt(
3324 &self,
3325 message_tx: &OutgoingMessageTx,
3326 ) -> Option<ResponderHandlerAttempt> {
3327 match self {
3328 Self::Request(target) => target.destination.begin_handler_attempt(message_tx.clone()),
3329 Self::Response(_) => None,
3330 }
3331 }
3332}
3333
3334#[derive(Debug)]
3335enum OutgoingMessage {
3336 /// Close the outgoing application queue and acknowledge after every
3337 /// already-accepted message has entered the raw transport queue.
3338 CloseAfterDraining { done: oneshot::Sender<()> },
3339
3340 /// Mark every entry in an incoming batch as dispatched. A completed
3341 /// response array may only be emitted after this barrier.
3342 BatchDispatchComplete { completion: BatchDispatchCompletion },
3343
3344 /// Finish arbitration for a handler attempt that may have dropped a batch
3345 /// responder immediately before returning an error.
3346 BatchHandlerAttemptComplete { destination: ResponseDestination },
3347
3348 /// Record that a claimed batch request dropped its responder without
3349 /// replying. The fallback remains provisional while its handler attempt is
3350 /// active so a handler error can supply the authoritative response.
3351 AbandonedBatchResponse {
3352 id: RequestId,
3353 method: String,
3354 destination: ResponseDestination,
3355 },
3356
3357 /// Send a request to the server.
3358 Request {
3359 /// id assigned to this request (generated by sender)
3360 id: RequestId,
3361
3362 /// the original method
3363 method: String,
3364
3365 /// The logical message before peer-direction wrapping.
3366 untyped: UntypedMessage,
3367
3368 /// How to transform the logical message for its target peer.
3369 remote_style: crate::role::RemoteStyle,
3370
3371 /// Optional prerequisite that must finish before the request becomes
3372 /// visible on the transport.
3373 readiness: Option<RequestReadiness>,
3374 },
3375
3376 /// Send a notification to the server.
3377 Notification {
3378 /// the message to send; this may have a distinct method
3379 /// depending on the peer
3380 untyped: UntypedMessage,
3381 },
3382
3383 /// Send a response to a message from the server
3384 Response {
3385 id: RequestId,
3386
3387 /// Method of the incoming request this response completes.
3388 method: String,
3389
3390 response: Result<serde_json::Value, crate::Error>,
3391
3392 destination: ResponseDestination,
3393 },
3394
3395 /// Send an Error Response that cannot be correlated to a request ID.
3396 UncorrelatedErrorResponse {
3397 error: crate::Error,
3398 destination: ResponseDestination,
3399 },
3400}
3401
3402impl OutgoingMessage {
3403 /// Kind, method, and ID only; params, results, and errors can carry
3404 /// credentials and must not appear in ordinary logs.
3405 fn log_summary(&self) -> (&'static str, Option<&str>, Option<&RequestId>) {
3406 match self {
3407 Self::CloseAfterDraining { .. } => ("close_after_draining", None, None),
3408 Self::BatchDispatchComplete { .. } => ("batch_dispatch_complete", None, None),
3409 Self::BatchHandlerAttemptComplete { .. } => {
3410 ("batch_handler_attempt_complete", None, None)
3411 }
3412 Self::AbandonedBatchResponse { id, method, .. } => {
3413 ("abandoned_batch_response", Some(method), Some(id))
3414 }
3415 Self::Request { id, method, .. } => ("request", Some(method), Some(id)),
3416 Self::Notification { untyped } => ("notification", Some(&untyped.method), None),
3417 Self::Response { id, method, .. } => ("response", Some(method), Some(id)),
3418 Self::UncorrelatedErrorResponse { .. } => ("uncorrelated_error_response", None, None),
3419 }
3420 }
3421}
3422
3423/// Return type from JrHandler; indicates whether the request was handled or not.
3424#[must_use]
3425#[derive(Debug)]
3426pub enum Handled<T> {
3427 /// The message was handled
3428 Yes,
3429
3430 /// The message was not handled; returns the original value.
3431 ///
3432 /// If `retry` is true,
3433 No {
3434 /// The message to be passed to subsequent handlers
3435 /// (typically the original message, but it may have been
3436 /// mutated.)
3437 message: T,
3438
3439 /// If true, request the message to be queued and retried with
3440 /// dynamic handlers as they are added.
3441 ///
3442 /// This is used for managing session updates since the dynamic
3443 /// handler for a session cannot be added until the response to the
3444 /// new session request has been processed and there may be updates
3445 /// that get processed at the same time.
3446 retry: bool,
3447 },
3448}
3449
3450/// Trait for converting handler return values into [`Handled`].
3451///
3452/// This trait allows handlers to return either `()` (which becomes `Handled::Yes`)
3453/// or an explicit `Handled<T>` value for more control over handler propagation.
3454pub trait IntoHandled<T> {
3455 /// Convert this value into a `Handled<T>`.
3456 fn into_handled(self) -> Handled<T>;
3457}
3458
3459impl<T> IntoHandled<T> for () {
3460 fn into_handled(self) -> Handled<T> {
3461 Handled::Yes
3462 }
3463}
3464
3465impl<T> IntoHandled<T> for Handled<T> {
3466 fn into_handled(self) -> Handled<T> {
3467 self
3468 }
3469}
3470
3471/// A protocol-v2 connection context.
3472///
3473/// Values of this type are supplied to callbacks registered on a
3474/// [`V2Builder`]. It exposes the general connection operations that are valid
3475/// for protocol v2 while keeping version-specific high-level helpers for other
3476/// protocol versions out of the typed context. The generic JSON-RPC send
3477/// methods remain intentionally schema-agnostic.
3478///
3479/// This is a thin, cheaply cloneable handle to the underlying JSON-RPC
3480/// connection. It intentionally does not implement [`Deref`](std::ops::Deref)
3481/// to [`ConnectionTo`].
3482#[cfg(feature = "unstable_protocol_v2")]
3483#[derive(Clone, Debug)]
3484pub struct V2ConnectionTo<Counterpart: Role> {
3485 inner: ConnectionTo<Counterpart>,
3486}
3487
3488#[cfg(feature = "unstable_protocol_v2")]
3489impl V2ConnectionTo<Agent> {
3490 /// Send the client's `initialize` request and report how the agent
3491 /// negotiated.
3492 ///
3493 /// Unlike [`send_request`](Self::send_request), a v1-only agent's valid v1
3494 /// response resolves successfully as [`InitializeOutcome::LowerVersion`]
3495 /// with the agent's typed response, so the caller can decide whether to
3496 /// fall back to a v1 client implementation. The connection stays
3497 /// uninitialized in that case; the SDK never converts it to v1.
3498 ///
3499 /// The request still resolves with an error for errors returned by the
3500 /// agent (including the `-32022` version mismatch), malformed responses,
3501 /// and transport failures.
3502 ///
3503 /// After an initialization completes, `initialize` may be sent again
3504 /// before any other request; the agent treats the latest one as
3505 /// authoritative. Once another request has been sent, a repeat resolves
3506 /// with an `-32600` error without being sent.
3507 ///
3508 /// ```no_run
3509 /// # use agent_client_protocol::schema::{ProtocolVersion, v2};
3510 /// # use agent_client_protocol::{
3511 /// # Client, ConnectTo, InitializeOutcome, SelectedInitializeResponse,
3512 /// # };
3513 /// # async fn example(agent: impl ConnectTo<Client>) -> agent_client_protocol::Result<()> {
3514 /// let outcome = Client
3515 /// .v2()
3516 /// .connect_with(agent, async |cx| {
3517 /// let request = v2::InitializeRequest::new(
3518 /// ProtocolVersion::V2,
3519 /// v2::Implementation::new("example", "0.1.0"),
3520 /// );
3521 /// match cx.send_initialize(request).block_task().await? {
3522 /// InitializeOutcome::Initialized(_response) => {
3523 /// // Continue with v2 traffic.
3524 /// Ok(None)
3525 /// }
3526 /// InitializeOutcome::LowerVersion(mismatch) => Ok(Some(mismatch)),
3527 /// _ => Err(agent_client_protocol::util::internal_error("unexpected outcome")),
3528 /// }
3529 /// })
3530 /// .await?;
3531 /// if let Some(mismatch) = outcome
3532 /// && let SelectedInitializeResponse::V1(_v1_response) = mismatch.response
3533 /// {
3534 /// // Reconnect with a v1 client implementation.
3535 /// }
3536 /// # Ok(())
3537 /// # }
3538 /// ```
3539 pub fn send_initialize(
3540 &self,
3541 request: crate::schema::v2::InitializeRequest,
3542 ) -> SentRequest<InitializeOutcome> {
3543 let mut prepared = self.inner.prepare_request_to_with_options(
3544 self.inner.counterpart(),
3545 protocol_compat::InitializeNegotiation(request),
3546 None,
3547 None,
3548 );
3549 prepared
3550 .publication
3551 .pending_reply
3552 .accepts_lower_initialize_version = true;
3553 prepared.into_sent_request(false)
3554 }
3555}
3556
3557#[cfg(feature = "unstable_protocol_v2")]
3558impl<Counterpart: Role> V2ConnectionTo<Counterpart> {
3559 /// Access the underlying version-neutral connection inside the SDK.
3560 pub(crate) fn raw_connection(&self) -> &ConnectionTo<Counterpart> {
3561 &self.inner
3562 }
3563
3564 /// Return the counterpart role this connection is talking to.
3565 pub fn counterpart(&self) -> Counterpart {
3566 self.inner.counterpart()
3567 }
3568
3569 /// Wait until the incoming transport reaches clean EOF.
3570 pub async fn incoming_closed(&self) {
3571 self.inner.incoming_closed().await;
3572 }
3573
3574 /// Return whether clean incoming-EOF processing has completed.
3575 #[must_use]
3576 pub fn is_incoming_closed(&self) -> bool {
3577 self.inner.is_incoming_closed()
3578 }
3579
3580 /// Wait until the connection driver has terminated, for any reason.
3581 ///
3582 /// See [`ConnectionTo::terminated`].
3583 pub async fn terminated(&self) -> ConnectionTermination {
3584 self.inner.terminated().await
3585 }
3586
3587 /// Return the termination reason if the connection driver has terminated.
3588 #[must_use]
3589 pub fn termination(&self) -> Option<ConnectionTermination> {
3590 self.inner.termination()
3591 }
3592
3593 /// Spawn a task that runs for as long as the JSON-RPC connection is served.
3594 #[track_caller]
3595 pub fn spawn(
3596 &self,
3597 task: impl IntoFuture<Output = Result<(), crate::Error>, IntoFuture: Send + 'static>,
3598 ) -> Result<(), crate::Error> {
3599 self.inner.spawn(task)
3600 }
3601
3602 /// Spawn a JSON-RPC connection in the background.
3603 ///
3604 /// The returned connection context is selected by `builder`; spawning a
3605 /// [`V2Builder`] therefore returns another [`V2ConnectionTo`].
3606 ///
3607 /// ```no_run
3608 /// # use agent_client_protocol::{
3609 /// # Agent, Client, ConnectTo, Error, V2ConnectionTo,
3610 /// # };
3611 /// # fn example(
3612 /// # connection: V2ConnectionTo<Agent>,
3613 /// # transport: impl ConnectTo<Client> + 'static,
3614 /// # ) -> Result<(), Error> {
3615 /// let child: V2ConnectionTo<Agent> =
3616 /// connection.spawn_connection(Client.v2(), transport)?;
3617 /// # drop(child);
3618 /// # Ok(())
3619 /// # }
3620 /// ```
3621 #[track_caller]
3622 pub fn spawn_connection<R: Role, Context: ConnectionContext>(
3623 &self,
3624 builder: Builder<
3625 R,
3626 impl HandleDispatchFrom<R::Counterpart> + 'static,
3627 impl RunWithConnectionTo<R::Counterpart> + 'static,
3628 impl HandleConnectionClose<R::Counterpart> + 'static,
3629 Context,
3630 >,
3631 transport: impl ConnectTo<R> + 'static,
3632 ) -> Result<Context::Connection<R::Counterpart>, crate::Error> {
3633 self.inner.spawn_connection_with_context(builder, transport)
3634 }
3635
3636 /// Send a request or notification and forward its response appropriately.
3637 pub fn send_proxied_message<Req: JsonRpcRequest<Response: Send>, Notif: JsonRpcNotification>(
3638 &self,
3639 message: Dispatch<Req, Notif>,
3640 ) -> Result<(), crate::Error>
3641 where
3642 Counterpart: HasPeer<Counterpart>,
3643 {
3644 self.inner.send_proxied_message(message)
3645 }
3646
3647 /// Send a request or notification to a specific peer and forward its
3648 /// response appropriately.
3649 pub fn send_proxied_message_to<
3650 Peer: Role,
3651 Req: JsonRpcRequest<Response: Send>,
3652 Notif: JsonRpcNotification,
3653 >(
3654 &self,
3655 peer: Peer,
3656 message: Dispatch<Req, Notif>,
3657 ) -> Result<(), crate::Error>
3658 where
3659 Counterpart: HasPeer<Peer>,
3660 {
3661 self.inner.send_proxied_message_to(peer, message)
3662 }
3663
3664 /// Send an outgoing request to the default counterpart peer.
3665 pub fn send_request<Req: JsonRpcRequest>(&self, request: Req) -> SentRequest<Req::Response>
3666 where
3667 Counterpart: HasPeer<Counterpart>,
3668 {
3669 self.inner.send_request(request)
3670 }
3671
3672 /// Send an outgoing request to a specific peer.
3673 pub fn send_request_to<Peer: Role, Req: JsonRpcRequest>(
3674 &self,
3675 peer: Peer,
3676 request: Req,
3677 ) -> SentRequest<Req::Response>
3678 where
3679 Counterpart: HasPeer<Peer>,
3680 {
3681 self.inner.send_request_to(peer, request)
3682 }
3683
3684 /// Prepare a request without sending it until response handling is selected.
3685 ///
3686 /// See [`ConnectionTo::prepare_request`] for publication and ordering semantics.
3687 pub fn prepare_request<Req: JsonRpcRequest>(
3688 &self,
3689 request: Req,
3690 ) -> PreparedRequest<Req::Response>
3691 where
3692 Counterpart: HasPeer<Counterpart>,
3693 {
3694 self.inner.prepare_request(request)
3695 }
3696
3697 /// Prepare a request to a specific peer without sending it.
3698 ///
3699 /// See [`ConnectionTo::prepare_request_to`] for publication and ordering semantics.
3700 pub fn prepare_request_to<Peer: Role, Req: JsonRpcRequest>(
3701 &self,
3702 peer: Peer,
3703 request: Req,
3704 ) -> PreparedRequest<Req::Response>
3705 where
3706 Counterpart: HasPeer<Peer>,
3707 {
3708 self.inner.prepare_request_to(peer, request)
3709 }
3710
3711 /// Send an outgoing notification to the default counterpart peer.
3712 pub fn send_notification<N: JsonRpcNotification>(
3713 &self,
3714 notification: N,
3715 ) -> Result<(), crate::Error>
3716 where
3717 Counterpart: HasPeer<Counterpart>,
3718 {
3719 self.inner.send_notification(notification)
3720 }
3721
3722 /// Send an outgoing notification to a specific peer.
3723 pub fn send_notification_to<Peer: Role, N: JsonRpcNotification>(
3724 &self,
3725 peer: Peer,
3726 notification: N,
3727 ) -> Result<(), crate::Error>
3728 where
3729 Counterpart: HasPeer<Peer>,
3730 {
3731 self.inner.send_notification_to(peer, notification)
3732 }
3733
3734 /// Send a `$/cancel_request` notification to the default counterpart peer.
3735 pub fn send_cancel_request(
3736 &self,
3737 request_id: impl Into<crate::schema::v1::RequestId>,
3738 ) -> Result<(), crate::Error>
3739 where
3740 Counterpart: HasPeer<Counterpart>,
3741 {
3742 self.inner.send_cancel_request(request_id)
3743 }
3744
3745 /// Send a `$/cancel_request` notification to a specific peer.
3746 pub fn send_cancel_request_to<Peer: Role>(
3747 &self,
3748 peer: Peer,
3749 request_id: impl Into<crate::schema::v1::RequestId>,
3750 ) -> Result<(), crate::Error>
3751 where
3752 Counterpart: HasPeer<Peer>,
3753 {
3754 self.inner.send_cancel_request_to(peer, request_id)
3755 }
3756
3757 /// Register a low-level dynamic message handler.
3758 ///
3759 /// Dynamic handlers use the version-neutral [`HandleDispatchFrom`] trait,
3760 /// so their callback receives the underlying [`ConnectionTo`]. Prefer
3761 /// typed handlers on [`V2Builder`] when registration can happen before the
3762 /// connection starts.
3763 ///
3764 /// ```no_run
3765 /// # use agent_client_protocol::{
3766 /// # Agent, Client, ConnectTo, DynamicHandlerGuard, Error, NullHandler,
3767 /// # };
3768 /// # async fn example(
3769 /// # transport: impl ConnectTo<Client> + 'static,
3770 /// # ) -> Result<(), Error> {
3771 /// Client.v2().connect_with(transport, async |connection| {
3772 /// let guard: DynamicHandlerGuard<Agent> =
3773 /// connection.add_dynamic_handler(NullHandler)?;
3774 ///
3775 /// // Keep `guard` alive for as long as the modal handler is needed.
3776 /// drop(guard);
3777 /// Ok(())
3778 /// }).await
3779 /// # }
3780 /// ```
3781 pub fn add_dynamic_handler(
3782 &self,
3783 handler: impl HandleDispatchFrom<Counterpart> + 'static,
3784 ) -> Result<DynamicHandlerGuard<Counterpart>, crate::Error> {
3785 self.inner.add_dynamic_handler(handler)
3786 }
3787}
3788
3789/// Connection context for sending messages and spawning tasks.
3790///
3791/// This is the primary handle for interacting with the JSON-RPC connection from
3792/// within handler callbacks. You can use it to:
3793///
3794/// * Send requests and notifications to the other side
3795/// * Spawn concurrent tasks that run alongside the connection
3796/// * Respond to requests (via [`Responder`] which wraps this)
3797///
3798/// # Cloning
3799///
3800/// `ConnectionTo` is cheaply cloneable - all clones refer to the same underlying connection.
3801/// This makes it easy to share across async tasks.
3802///
3803/// # Event Loop and Concurrency
3804///
3805/// Handler callbacks run on the event loop, which means the connection cannot process new
3806/// messages while your handler is running. Use [`spawn`](Self::spawn) to offload any
3807/// expensive or blocking work to concurrent tasks.
3808///
3809/// See the [Event Loop and Concurrency](Builder#event-loop-and-concurrency) section
3810/// for more details.
3811#[derive(Clone, Debug)]
3812pub struct ConnectionTo<Counterpart: Role> {
3813 counterpart: Counterpart,
3814 message_tx: OutgoingMessageTx,
3815 task_tx: TaskTx,
3816 dynamic_handler_tx: mpsc::UnboundedSender<DynamicHandlerMessage<Counterpart>>,
3817 transport_completion: SharedTransportCompletion,
3818 pending_replies: PendingRepliesRegistrar,
3819 #[cfg_attr(
3820 not(feature = "unstable_protocol_v2"),
3821 allow(
3822 dead_code,
3823 reason = "retained so ConnectionTo has one constructor shape"
3824 )
3825 )]
3826 protocol_mode: ProtocolMode,
3827 incoming_closed: IncomingClosed,
3828 protected_operations: Arc<Mutex<ProtectedOperations>>,
3829 runner_error_scope: Option<run::RunnerErrorScope>,
3830}
3831
3832type SharedTransportCompletion = future::Shared<BoxFuture<'static, Result<(), crate::Error>>>;
3833
3834type SharedCompletionSignal = future::Shared<BoxFuture<'static, ()>>;
3835
3836#[derive(Default)]
3837struct ProtectedOperations {
3838 pending: Vec<oneshot::Receiver<()>>,
3839 joining: Option<SharedCompletionSignal>,
3840}
3841
3842impl Debug for ProtectedOperations {
3843 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3844 formatter
3845 .debug_struct("ProtectedOperations")
3846 .field("pending", &self.pending.len())
3847 .field("joining", &self.joining.is_some())
3848 .finish_non_exhaustive()
3849 }
3850}
3851
3852fn completion_signal() -> (oneshot::Sender<()>, SharedCompletionSignal) {
3853 let (tx, rx) = oneshot::channel();
3854 let signal = async move {
3855 // Dropping a sender (e.g. foreground failure) is not success.
3856 if rx.await.is_err() {
3857 future::pending::<()>().await;
3858 }
3859 }
3860 .boxed()
3861 .shared();
3862 (tx, signal)
3863}
3864
3865#[derive(Clone)]
3866struct IncomingClosed {
3867 state: Arc<IncomingClosedState>,
3868}
3869
3870struct IncomingClosedState {
3871 closing: AtomicBool,
3872 closed: AtomicBool,
3873 signal_tx: Mutex<Option<oneshot::Sender<()>>>,
3874 signal_rx: future::Shared<BoxFuture<'static, ()>>,
3875 shutdown_tx: Mutex<Option<oneshot::Sender<()>>>,
3876 #[cfg(any(feature = "unstable_mcp_over_acp", test))]
3877 shutdown_rx: SharedCompletionSignal,
3878 termination: Mutex<Option<ConnectionTermination>>,
3879 terminated_tx: Mutex<Option<oneshot::Sender<()>>>,
3880 terminated_rx: SharedCompletionSignal,
3881}
3882
3883impl IncomingClosed {
3884 fn new() -> Self {
3885 let (signal_tx, signal_rx) = oneshot::channel();
3886 let (shutdown_tx, shutdown_rx) = oneshot::channel();
3887 #[cfg(not(any(feature = "unstable_mcp_over_acp", test)))]
3888 drop(shutdown_rx);
3889 let (terminated_tx, terminated_rx) = oneshot::channel();
3890 Self {
3891 state: Arc::new(IncomingClosedState {
3892 closing: AtomicBool::new(false),
3893 closed: AtomicBool::new(false),
3894 signal_tx: Mutex::new(Some(signal_tx)),
3895 signal_rx: signal_rx.map(|_| ()).boxed().shared(),
3896 shutdown_tx: Mutex::new(Some(shutdown_tx)),
3897 #[cfg(any(feature = "unstable_mcp_over_acp", test))]
3898 shutdown_rx: shutdown_rx.map(|_| ()).boxed().shared(),
3899 termination: Mutex::new(None),
3900 terminated_tx: Mutex::new(Some(terminated_tx)),
3901 terminated_rx: terminated_rx.map(|_| ()).boxed().shared(),
3902 }),
3903 }
3904 }
3905
3906 fn begin_close(&self) {
3907 self.state.closing.store(true, Ordering::Release);
3908 self.request_shutdown();
3909 }
3910
3911 fn request_shutdown(&self) {
3912 if let Some(tx) = self
3913 .state
3914 .shutdown_tx
3915 .lock()
3916 .expect("shutdown signal mutex poisoned")
3917 .take()
3918 {
3919 let _ = tx.send(());
3920 }
3921 }
3922
3923 fn finish_close(&self) {
3924 self.state.closed.store(true, Ordering::Release);
3925 let signal_tx = self
3926 .state
3927 .signal_tx
3928 .lock()
3929 .expect("incoming-close signal mutex poisoned")
3930 .take();
3931
3932 if let Some(signal_tx) = signal_tx {
3933 let _ = signal_tx.send(());
3934 }
3935 }
3936
3937 async fn closed(&self) {
3938 self.state.signal_rx.clone().await;
3939 }
3940
3941 fn is_closed(&self) -> bool {
3942 self.state.closed.load(Ordering::Acquire)
3943 }
3944
3945 fn is_closing(&self) -> bool {
3946 self.state.closing.load(Ordering::Acquire)
3947 }
3948
3949 /// Record the termination reason. Returns `false` if one was already set.
3950 fn set_termination(&self, termination: ConnectionTermination) -> bool {
3951 let mut slot = self
3952 .state
3953 .termination
3954 .lock()
3955 .expect("termination mutex poisoned");
3956 if slot.is_some() {
3957 return false;
3958 }
3959 *slot = Some(termination);
3960 true
3961 }
3962
3963 fn signal_terminated(&self) {
3964 if let Some(tx) = self
3965 .state
3966 .terminated_tx
3967 .lock()
3968 .expect("terminated signal mutex poisoned")
3969 .take()
3970 {
3971 let _ = tx.send(());
3972 }
3973 }
3974
3975 fn termination(&self) -> Option<ConnectionTermination> {
3976 self.state
3977 .termination
3978 .lock()
3979 .expect("termination mutex poisoned")
3980 .clone()
3981 }
3982
3983 async fn terminated(&self) -> ConnectionTermination {
3984 self.state.terminated_rx.clone().await;
3985 self.termination()
3986 .expect("termination is recorded before it is signalled")
3987 }
3988}
3989
3990/// Publishes the connection's termination exactly once: when the driver
3991/// completes via [`Self::finish`], or synchronously when the driver future is
3992/// dropped first.
3993struct TerminationGuard {
3994 incoming_closed: IncomingClosed,
3995 pending_replies: PendingReplies,
3996}
3997
3998impl TerminationGuard {
3999 fn finish(self, termination: ConnectionTermination) {
4000 self.publish(termination);
4001 }
4002
4003 fn publish(&self, termination: ConnectionTermination) {
4004 if !self.incoming_closed.set_termination(termination.clone()) {
4005 return;
4006 }
4007 // Record first, so a requester woken by its failure observes the
4008 // termination; then fail requests before waking `terminated()`.
4009 let pending_reply_count = self.pending_replies.terminate(&termination);
4010 tracing::debug!(
4011 termination = termination.kind(),
4012 pending_reply_count,
4013 "Connection terminated"
4014 );
4015 self.incoming_closed.signal_terminated();
4016 }
4017}
4018
4019impl Drop for TerminationGuard {
4020 fn drop(&mut self) {
4021 // No-op after `finish`.
4022 self.publish(ConnectionTermination::Dropped);
4023 }
4024}
4025
4026impl Debug for IncomingClosed {
4027 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4028 formatter
4029 .debug_struct("IncomingClosed")
4030 .field("is_closing", &self.is_closing())
4031 .field("is_closed", &self.is_closed())
4032 .finish_non_exhaustive()
4033 }
4034}
4035
4036/// Stable discriminator stored in the `data.reason` field of errors produced
4037/// when the incoming transport reaches clean EOF before a request receives its
4038/// response.
4039pub const INCOMING_TRANSPORT_CLOSED_REASON: &str = "incoming_transport_closed";
4040
4041/// Return whether `error` reports that the incoming transport reached clean
4042/// EOF before a request received its response.
4043#[must_use]
4044pub fn is_incoming_transport_closed(error: &crate::Error) -> bool {
4045 error
4046 .data
4047 .as_ref()
4048 .and_then(|data| data.get("reason"))
4049 .and_then(serde_json::Value::as_str)
4050 == Some(INCOMING_TRANSPORT_CLOSED_REASON)
4051}
4052
4053fn incoming_transport_closed_error(method: &str) -> crate::Error {
4054 let mut error = crate::Error::internal_error();
4055 error.message = "Incoming transport closed".to_string();
4056 error.data(serde_json::json!({
4057 "reason": INCOMING_TRANSPORT_CLOSED_REASON,
4058 "method": method,
4059 }))
4060}
4061
4062/// Unlike the cleanup coordinator below, check success before polling delivery:
4063/// an already-ready success must not resume a message handler into another
4064/// dispatch (including another entry of the same batch).
4065fn run_incoming_until_foreground_succeeds(
4066 incoming: impl Future<Output = Result<(), crate::Error>>,
4067 foreground_succeeded: SharedCompletionSignal,
4068 incoming_closed: IncomingClosed,
4069) -> impl Future<Output = Result<(), crate::Error>> {
4070 let mut incoming = Box::pin(incoming);
4071 future::poll_fn(move |cx| {
4072 if foreground_succeeded.clone().poll_unpin(cx).is_ready() && !incoming_closed.is_closing() {
4073 return std::task::Poll::Ready(Ok(()));
4074 }
4075 // A close callback already underway is protected. Its result is polled
4076 // before stopping, so callback errors retain their existing precedence.
4077 incoming.as_mut().poll(cx)
4078 })
4079}
4080
4081/// Run the connection background alongside its foreground while ensuring that
4082/// a foreground woken by incoming EOF cannot cancel close callbacks midway.
4083fn run_until_connection_close<R>(
4084 background: impl Future<Output = Result<(), crate::Error>>,
4085 foreground: impl Future<Output = Result<R, crate::Error>>,
4086 incoming_closed: IncomingClosed,
4087) -> impl Future<Output = Result<R, crate::Error>> {
4088 // Box these before constructing the returned future. Keeping the generic
4089 // connection actors directly in this async state would substantially grow
4090 // every `connect_*` future.
4091 let background = Box::pin(background);
4092 let foreground = Box::pin(foreground);
4093
4094 async move {
4095 match future::select(background, foreground).await {
4096 Either::Left((background_result, foreground)) => {
4097 background_result?;
4098 foreground.await
4099 }
4100 Either::Right((foreground_result, background)) => {
4101 if !incoming_closed.is_closing() {
4102 return foreground_result;
4103 }
4104
4105 match future::select(background, Box::pin(incoming_closed.closed())).await {
4106 Either::Left((background_result, _)) => {
4107 background_result?;
4108 foreground_result
4109 }
4110 Either::Right(((), background)) => {
4111 // Poll the background first once more so an error returned
4112 // by the just-finished close callback wins over the ready
4113 // foreground result.
4114 crate::util::run_until(background, future::ready(foreground_result)).await
4115 }
4116 }
4117 }
4118 }
4119 }
4120}
4121
4122impl<Counterpart: Role> ConnectionTo<Counterpart> {
4123 fn new(
4124 counterpart: Counterpart,
4125 message_tx: mpsc::UnboundedSender<OutgoingMessage>,
4126 task_tx: mpsc::UnboundedSender<Task>,
4127 dynamic_handler_tx: mpsc::UnboundedSender<DynamicHandlerMessage<Counterpart>>,
4128 transport_completion: SharedTransportCompletion,
4129 pending_replies: PendingRepliesRegistrar,
4130 protocol_mode: ProtocolMode,
4131 ) -> Self {
4132 Self {
4133 counterpart,
4134 message_tx,
4135 task_tx,
4136 dynamic_handler_tx,
4137 transport_completion,
4138 pending_replies,
4139 protocol_mode,
4140 incoming_closed: IncomingClosed::new(),
4141 protected_operations: Arc::default(),
4142 runner_error_scope: None,
4143 }
4144 }
4145
4146 pub(crate) fn with_runner_error_scope(mut self, scope: run::RunnerErrorScope) -> Self {
4147 self.runner_error_scope = Some(scope);
4148 self
4149 }
4150
4151 pub(crate) fn finish_runner_error(
4152 &self,
4153 error: crate::Error,
4154 ) -> impl Future<Output = ()> + Send + '_ {
4155 if let Some(scope) = &self.runner_error_scope {
4156 Either::Left(scope.finish(error))
4157 } else {
4158 // Unscoped runners belong to the connection itself.
4159 self.request_shutdown();
4160 Either::Right(self.wait_protected_operations())
4161 }
4162 }
4163
4164 /// Spawn only a connection-owned supervisor whose async cleanup must finish
4165 /// before the driver returns. Ordinary application work must use `spawn`.
4166 #[cfg(any(feature = "unstable_mcp_over_acp", test))]
4167 #[track_caller]
4168 pub(crate) fn spawn_protected(
4169 &self,
4170 task: impl IntoFuture<Output = Result<(), crate::Error>, IntoFuture: Send + 'static>,
4171 ) -> Result<(), crate::Error> {
4172 let mut state = self
4173 .protected_operations
4174 .lock()
4175 .expect("protected operations mutex poisoned");
4176 if state.joining.is_some() {
4177 return Err(crate::Error::request_cancelled());
4178 }
4179 // Reap completed acknowledgments at admission, rather than retaining
4180 // every operation for the entire lifetime of the connection.
4181 state
4182 .pending
4183 .retain_mut(|done| matches!(done.try_recv(), Ok(None)));
4184 let (done_tx, done_rx) = oneshot::channel();
4185 let task = task.into_future();
4186 self.spawn(async move {
4187 let result = task.await;
4188 let _ = done_tx.send(());
4189 result
4190 })?;
4191 state.pending.push(done_rx);
4192 Ok(())
4193 }
4194
4195 pub(crate) async fn wait_protected_operations(&self) {
4196 let joining = {
4197 let mut state = self
4198 .protected_operations
4199 .lock()
4200 .expect("protected operations mutex poisoned");
4201 if state.joining.is_none() {
4202 let operations = std::mem::take(&mut state.pending);
4203 state.joining = Some(
4204 async move {
4205 for operation in operations {
4206 let _ = operation.await;
4207 }
4208 }
4209 .boxed()
4210 .shared(),
4211 );
4212 }
4213 state.joining.as_ref().expect("join initialized").clone()
4214 };
4215 joining.await;
4216 }
4217
4218 pub(crate) fn request_shutdown(&self) {
4219 self.incoming_closed.request_shutdown();
4220 }
4221
4222 /// Early cancellation for owned native work, before close callbacks or drain.
4223 #[cfg(any(feature = "unstable_mcp_over_acp", test))]
4224 pub(crate) async fn shutdown_requested(&self) {
4225 self.incoming_closed.state.shutdown_rx.clone().await;
4226 }
4227
4228 #[cfg(feature = "unstable_protocol_v2")]
4229 pub(crate) fn acp_protocol_version(&self) -> Option<crate::schema::ProtocolVersion> {
4230 self.protocol_mode.api_protocol_version()
4231 }
4232
4233 /// Return the counterpart role this connection is talking to.
4234 pub fn counterpart(&self) -> Counterpart {
4235 self.counterpart.clone()
4236 }
4237
4238 /// Wait until the incoming transport reaches clean EOF.
4239 ///
4240 /// Transport closure means that no more messages or responses can arrive.
4241 /// Pending requests are failed first; this completes after registered
4242 /// [`Builder::on_close`] callbacks finish.
4243 /// It does not automatically cancel the future passed to
4244 /// [`Builder::connect_with`]; use [`Builder::on_close`] when the connection
4245 /// should run application-specific cleanup or terminate that future.
4246 ///
4247 /// This never completes if the connection ends some other way; use
4248 /// [`terminated`](Self::terminated) to observe every termination.
4249 pub async fn incoming_closed(&self) {
4250 self.incoming_closed.closed().await;
4251 }
4252
4253 /// Return whether clean incoming-EOF processing has completed.
4254 ///
4255 /// This remains `false` while [`Builder::on_close`] callbacks are running.
4256 #[must_use]
4257 pub fn is_incoming_closed(&self) -> bool {
4258 self.incoming_closed.is_closed()
4259 }
4260
4261 /// Wait until the connection driver has terminated, for any reason.
4262 ///
4263 /// This is the universal terminal event for a connection driven by
4264 /// [`Builder::connect_to`] or [`Builder::connect_with`]. Unlike
4265 /// [`incoming_closed`](Self::incoming_closed), it also completes after
4266 /// transport errors, handler or close-callback errors, foreground
4267 /// completion, and a dropped driver future.
4268 ///
4269 /// The termination is published once, synchronously, as the driver future
4270 /// returns (or is dropped). At that point:
4271 ///
4272 /// - No further handler, close callback, runner, or
4273 /// [`spawn`](Self::spawn)ed task of this connection will run; each has
4274 /// completed or been dropped.
4275 /// - Every request still awaiting a response has been failed. Requests
4276 /// failed earlier by clean incoming EOF satisfy
4277 /// [`is_incoming_transport_closed`](crate::is_incoming_transport_closed);
4278 /// the rest satisfy [`is_connection_terminated`](crate::is_connection_terminated),
4279 /// as do requests sent afterwards.
4280 ///
4281 /// Do not await this from inside the connection's own handlers, close
4282 /// callbacks, or tasks: termination happens only after they finish.
4283 ///
4284 /// Termination does not reap child processes owned by the transport, nor
4285 /// drain application-owned queues.
4286 pub async fn terminated(&self) -> ConnectionTermination {
4287 self.incoming_closed.terminated().await
4288 }
4289
4290 /// Return the termination reason if the connection driver has terminated.
4291 ///
4292 /// See [`terminated`](Self::terminated).
4293 #[must_use]
4294 pub fn termination(&self) -> Option<ConnectionTermination> {
4295 self.incoming_closed.termination()
4296 }
4297
4298 /// Stop accepting outgoing messages, drain routable output through the
4299 /// protocol actor, and finish cooperative physical sinks. Reactive serving also
4300 /// joins owned transport work after incoming EOF.
4301 async fn drain_outgoing(
4302 &self,
4303 finish: Option<crate::component::FinishControl>,
4304 wait_owned_transport: bool,
4305 ) -> Result<(), crate::Error> {
4306 let (done_tx, done_rx) = oneshot::channel();
4307 let marker_result = send_raw_message(
4308 &self.message_tx,
4309 OutgoingMessage::CloseAfterDraining { done: done_tx },
4310 );
4311 let marker_result = match marker_result {
4312 Ok(()) => done_rx.await.map_err(|error| {
4313 crate::util::internal_error(format!(
4314 "outgoing drain marker was dropped before completion: {error}"
4315 ))
4316 }),
4317 Err(error) => Err(error),
4318 };
4319
4320 let physical_finish = finish.is_some();
4321 if let Some(mut finish) = finish {
4322 // Only finish the physical sink after the protocol actor has handed
4323 // off its accepted output. Closing it earlier races the drain.
4324 finish.request();
4325 }
4326 if physical_finish || wait_owned_transport {
4327 // Cooperative completion proves physical sink drain. Reactive serving
4328 // also waits for owned work after EOF (e.g. child exit status).
4329 self.transport_completion.clone().await?;
4330 }
4331 // Opaque application drivers have no physical finish contract. Keep
4332 // polling their errors in the background, but do not globally join work
4333 // which may intentionally run forever after the foreground returns.
4334 marker_result
4335 }
4336
4337 fn is_incoming_closing(&self) -> bool {
4338 self.incoming_closed.is_closing()
4339 }
4340
4341 pub(super) fn begin_incoming_close(&self) {
4342 self.incoming_closed.begin_close();
4343 }
4344
4345 pub(super) fn finish_incoming_close(&self) {
4346 self.incoming_closed.finish_close();
4347 }
4348
4349 /// Spawns a task that will run so long as the JSON-RPC connection is being served.
4350 ///
4351 /// This is the primary mechanism for offloading expensive work from handler callbacks
4352 /// to avoid blocking the event loop. Spawned tasks run concurrently with the connection,
4353 /// allowing the server to continue processing messages.
4354 ///
4355 /// # Event Loop
4356 ///
4357 /// Handler callbacks run on the event loop, which cannot process new messages while
4358 /// your handler is running. Use `spawn` for any expensive operations:
4359 ///
4360 /// ```no_run
4361 /// # use agent_client_protocol_test::*;
4362 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
4363 /// # let connection = mock_connection();
4364 /// connection.on_receive_request(async |req: ProcessRequest, responder, cx| {
4365 /// // Clone cx for the spawned task
4366 /// cx.spawn({
4367 /// let connection = cx.clone();
4368 /// async move {
4369 /// let result = expensive_operation(&req.data).await?;
4370 /// connection.send_notification(ProcessComplete { result })?;
4371 /// Ok(())
4372 /// }
4373 /// })?;
4374 ///
4375 /// // Respond immediately
4376 /// responder.respond(ProcessResponse { result: "started".into() })
4377 /// }, agent_client_protocol::on_receive_request!())
4378 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
4379 /// # Ok(())
4380 /// # }
4381 /// ```
4382 ///
4383 /// # Errors
4384 ///
4385 /// If the spawned task returns an error, the entire server will shut down.
4386 #[track_caller]
4387 pub fn spawn(
4388 &self,
4389 task: impl IntoFuture<Output = Result<(), crate::Error>, IntoFuture: Send + 'static>,
4390 ) -> Result<(), crate::Error> {
4391 let location = std::panic::Location::caller();
4392 let task = task.into_future();
4393 Task::new(location, task).spawn(&self.task_tx)
4394 }
4395
4396 /// Spawn a JSON-RPC connection in the background and return a raw
4397 /// [`ConnectionTo`] for it.
4398 ///
4399 /// This is useful for creating multiple connections that communicate with each other,
4400 /// such as implementing proxy patterns or connecting to multiple backend services.
4401 ///
4402 /// # Arguments
4403 ///
4404 /// - `builder`: The connection builder with handlers configured
4405 /// - `transport`: The transport component to connect to
4406 ///
4407 /// # Returns
4408 ///
4409 /// The child builder may select any callback context. For example, this
4410 /// method can spawn a `V2Builder`, whose callbacks receive
4411 /// `V2ConnectionTo`, while preserving this method's existing raw return
4412 /// type and single explicit role parameter.
4413 ///
4414 /// When a raw parent also needs the builder-selected child handle, use the
4415 /// protocol-v2 `spawn_connection_with_context` method.
4416 ///
4417 /// # Example: Proxying to a backend connection
4418 ///
4419 /// ```
4420 /// # use agent_client_protocol::UntypedRole;
4421 /// # use agent_client_protocol::{Builder, ConnectionTo};
4422 /// # use agent_client_protocol_test::*;
4423 /// # async fn example(cx: ConnectionTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
4424 /// // Set up a backend connection builder
4425 /// let backend = UntypedRole.builder()
4426 /// .on_receive_request(async |req: MyRequest, responder, _cx| {
4427 /// responder.respond(MyResponse { status: "ok".into() })
4428 /// }, agent_client_protocol::on_receive_request!());
4429 ///
4430 /// // Spawn it and get a context to send requests to it
4431 /// let backend_connection = cx.spawn_connection::<UntypedRole>(backend, MockTransport)?;
4432 ///
4433 /// // Now you can forward requests to the backend
4434 /// let response = backend_connection.send_request(MyRequest {}).block_task().await?;
4435 /// # Ok(())
4436 /// # }
4437 /// ```
4438 #[track_caller]
4439 pub fn spawn_connection<R: Role>(
4440 &self,
4441 builder: Builder<
4442 R,
4443 impl HandleDispatchFrom<R::Counterpart> + 'static,
4444 impl RunWithConnectionTo<R::Counterpart> + 'static,
4445 impl HandleConnectionClose<R::Counterpart> + 'static,
4446 impl ConnectionContext,
4447 >,
4448 transport: impl ConnectTo<R> + 'static,
4449 ) -> Result<ConnectionTo<R::Counterpart>, crate::Error> {
4450 self.spawn_connection_raw(builder, transport)
4451 }
4452
4453 /// Spawn a JSON-RPC connection and return the connection context selected
4454 /// by its builder.
4455 ///
4456 /// This is the low-level counterpart to
4457 /// [`V2ConnectionTo::spawn_connection`] for code that intentionally works
4458 /// with a raw [`ConnectionTo`], such as custom [`HandleDispatchFrom`] or
4459 /// [`RunWithConnectionTo`] implementations. Prefer [`Self::spawn_connection`]
4460 /// when a raw child handle is sufficient.
4461 ///
4462 /// ```no_run
4463 /// # use agent_client_protocol::{
4464 /// # Agent, Client, ConnectTo, ConnectionTo, Error, UntypedRole,
4465 /// # V2ConnectionTo,
4466 /// # };
4467 /// # fn example(
4468 /// # connection: ConnectionTo<UntypedRole>,
4469 /// # transport: impl ConnectTo<Client> + 'static,
4470 /// # ) -> Result<(), Error> {
4471 /// let child: V2ConnectionTo<Agent> =
4472 /// connection.spawn_connection_with_context(Client.v2(), transport)?;
4473 /// # drop(child);
4474 /// # Ok(())
4475 /// # }
4476 /// ```
4477 #[cfg(feature = "unstable_protocol_v2")]
4478 #[track_caller]
4479 pub fn spawn_connection_with_context<R: Role, Context: ConnectionContext>(
4480 &self,
4481 builder: Builder<
4482 R,
4483 impl HandleDispatchFrom<R::Counterpart> + 'static,
4484 impl RunWithConnectionTo<R::Counterpart> + 'static,
4485 impl HandleConnectionClose<R::Counterpart> + 'static,
4486 Context,
4487 >,
4488 transport: impl ConnectTo<R> + 'static,
4489 ) -> Result<Context::Connection<R::Counterpart>, crate::Error> {
4490 let connection = self.spawn_connection_raw(builder, transport)?;
4491 Ok(connection_context::from_raw::<Context, _>(connection))
4492 }
4493
4494 #[track_caller]
4495 fn spawn_connection_raw<R: Role, Context: ConnectionContext>(
4496 &self,
4497 builder: Builder<
4498 R,
4499 impl HandleDispatchFrom<R::Counterpart> + 'static,
4500 impl RunWithConnectionTo<R::Counterpart> + 'static,
4501 impl HandleConnectionClose<R::Counterpart> + 'static,
4502 Context,
4503 >,
4504 transport: impl ConnectTo<R> + 'static,
4505 ) -> Result<ConnectionTo<R::Counterpart>, crate::Error> {
4506 let (connection, future) =
4507 builder.into_connection_and_future(transport, false, |_| std::future::pending());
4508 Task::new(std::panic::Location::caller(), future).spawn(&self.task_tx)?;
4509 Ok(connection)
4510 }
4511
4512 /// Send a request/notification and forward the response appropriately.
4513 ///
4514 /// The request context's response type matches the request's response type,
4515 /// enabling type-safe message forwarding.
4516 pub fn send_proxied_message<Req: JsonRpcRequest<Response: Send>, Notif: JsonRpcNotification>(
4517 &self,
4518 message: Dispatch<Req, Notif>,
4519 ) -> Result<(), crate::Error>
4520 where
4521 Counterpart: HasPeer<Counterpart>,
4522 {
4523 self.send_proxied_message_to(self.counterpart(), message)
4524 }
4525
4526 /// Send a request/notification and forward the response appropriately.
4527 ///
4528 /// The request context's response type matches the request's response type,
4529 /// enabling type-safe message forwarding.
4530 ///
4531 /// `$/cancel_request` notifications are *not* forwarded: their `requestId`
4532 /// refers to a request on the connection they arrived over and would be
4533 /// meaningless to `peer`. Cancellation instead propagates hop by hop,
4534 /// because the responders passed to
4535 /// [`forward_response_to`](SentRequest::forward_response_to) observe it
4536 /// and re-issue the cancellation with the forwarded request's own ID.
4537 pub fn send_proxied_message_to<
4538 Peer: Role,
4539 Req: JsonRpcRequest<Response: Send>,
4540 Notif: JsonRpcNotification,
4541 >(
4542 &self,
4543 peer: Peer,
4544 message: Dispatch<Req, Notif>,
4545 ) -> Result<(), crate::Error>
4546 where
4547 Counterpart: HasPeer<Peer>,
4548 {
4549 match message {
4550 Dispatch::Request(request, responder) => self
4551 .send_ordered_request_to(peer, request)
4552 .forward_response_to(responder),
4553 Dispatch::Notification(notification) => {
4554 // `$/cancel_request` is connection-scoped: its `requestId` was
4555 // allocated on the connection the notification arrived over
4556 // and means nothing to `peer`. The cancellation has already
4557 // been recorded on this connection's responder markers, and
4558 // `forward_response_to` re-issues it for the forwarded request
4559 // with the correct per-hop ID, so drop the raw notification
4560 // instead of tunneling a meaningless ID across the hop.
4561 if is_cancel_request_notification(¬ification) {
4562 tracing::debug!(
4563 "not forwarding hop-scoped `$/cancel_request` notification across proxy hop"
4564 );
4565 return Ok(());
4566 }
4567 self.send_notification_to(peer, notification)
4568 }
4569 Dispatch::Response(result, router) => {
4570 // Responses are forwarded directly to their destination
4571 router.route_with_result(result)
4572 }
4573 }
4574 }
4575
4576 /// Send an outgoing request and return a [`SentRequest`] for handling the reply.
4577 ///
4578 /// The returned [`SentRequest`] makes the response-consumption mode explicit:
4579 ///
4580 /// * [`on_receiving_result`](SentRequest::on_receiving_result) - Register a callback and
4581 /// return immediately. If registered before the response is routed during its original
4582 /// dispatch, the loop waits for the callback to complete.
4583 /// * [`block_task`](SentRequest::block_task) - Wait on the current task until the response
4584 /// arrives. This is only safe when that task already runs outside the dispatch loop.
4585 ///
4586 /// For callback ordering selected before publication, use
4587 /// [`prepare_request`](Self::prepare_request) instead. Even an immediately
4588 /// chained callback can race with a fast response on a concurrent connection.
4589 ///
4590 /// # Anti-Footgun Design
4591 ///
4592 /// The API intentionally makes it difficult to block on the result directly to prevent
4593 /// the common mistake of blocking the event loop while waiting for a response:
4594 ///
4595 /// ```compile_fail
4596 /// # use agent_client_protocol_test::*;
4597 /// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
4598 /// // ❌ This doesn't compile - prevents blocking the event loop
4599 /// let response = cx.send_request(MyRequest {}).await?;
4600 /// # Ok(())
4601 /// # }
4602 /// ```
4603 ///
4604 /// ```no_run
4605 /// # use agent_client_protocol_test::*;
4606 /// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
4607 /// // ✅ Option 1: Register an ordered callback (safe in handlers)
4608 /// cx.send_request(MyRequest {})
4609 /// .on_receiving_result(async |result| {
4610 /// // Handle the response
4611 /// Ok(())
4612 /// })?;
4613 ///
4614 /// // ✅ Option 2: Block in spawned task (safe because task is concurrent)
4615 /// cx.spawn({
4616 /// let cx = cx.clone();
4617 /// async move {
4618 /// let response = cx.send_request(MyRequest {})
4619 /// .block_task()
4620 /// .await?;
4621 /// // Process response...
4622 /// Ok(())
4623 /// }
4624 /// })?;
4625 /// # Ok(())
4626 /// # }
4627 /// ```
4628 /// Send an outgoing request to the default counterpart peer.
4629 ///
4630 /// This is a convenience method that sends to the counterpart role `R`.
4631 /// For explicit control over the target peer, use [`send_request_to`](Self::send_request_to).
4632 pub fn send_request<Req: JsonRpcRequest>(&self, request: Req) -> SentRequest<Req::Response>
4633 where
4634 Counterpart: HasPeer<Counterpart>,
4635 {
4636 self.send_request_to(self.counterpart.clone(), request)
4637 }
4638
4639 /// Send an outgoing request to a specific peer.
4640 ///
4641 /// The message will be transformed according to the [`HasPeer`](crate::role::HasPeer)
4642 /// implementation before being sent.
4643 pub fn send_request_to<Peer: Role, Req: JsonRpcRequest>(
4644 &self,
4645 peer: Peer,
4646 request: Req,
4647 ) -> SentRequest<Req::Response>
4648 where
4649 Counterpart: HasPeer<Peer>,
4650 {
4651 self.send_request_to_with_options(peer, request, false, None, None)
4652 }
4653
4654 /// Prepare a request without sending it until response handling is selected.
4655 ///
4656 /// Unlike [`send_request`](Self::send_request), this does not register a
4657 /// pending reply or enqueue the request. A consuming method on the returned
4658 /// [`PreparedRequest`] publishes it synchronously. Callback-style methods
4659 /// select ordered consumption before publication, closing the race with a
4660 /// fast peer response even when the connection runs on another task.
4661 ///
4662 /// Dropping the prepared request sends nothing. See [`PreparedRequest`] for
4663 /// the available consumption modes and their error and cancellation behavior.
4664 ///
4665 /// # Examples
4666 ///
4667 /// ```no_run
4668 /// # use agent_client_protocol::{ConnectionTo, Error, UntypedRole};
4669 /// # use agent_client_protocol_test::MyRequest;
4670 /// # fn example(connection: ConnectionTo<UntypedRole>) -> Result<(), Error> {
4671 /// connection.prepare_request(MyRequest {}).on_receiving_result(async |result| {
4672 /// let response = result?;
4673 /// // Apply bounded response work before later inbound messages.
4674 /// Ok(())
4675 /// })?;
4676 /// # Ok(())
4677 /// # }
4678 /// ```
4679 pub fn prepare_request<Req: JsonRpcRequest>(
4680 &self,
4681 request: Req,
4682 ) -> PreparedRequest<Req::Response>
4683 where
4684 Counterpart: HasPeer<Counterpart>,
4685 {
4686 self.prepare_request_to(self.counterpart.clone(), request)
4687 }
4688
4689 /// Prepare a request to a specific peer without sending it.
4690 ///
4691 /// The request is serialized now. A consuming method synchronously registers
4692 /// its pending reply and enqueues the request; peer transformation and
4693 /// transmission happen later in the connection driver.
4694 /// See [`prepare_request`](Self::prepare_request).
4695 pub fn prepare_request_to<Peer: Role, Req: JsonRpcRequest>(
4696 &self,
4697 peer: Peer,
4698 request: Req,
4699 ) -> PreparedRequest<Req::Response>
4700 where
4701 Counterpart: HasPeer<Peer>,
4702 {
4703 self.prepare_request_to_with_options(peer, request, None, None)
4704 }
4705
4706 /// Send a request and run a synchronous side effect when its valid success
4707 /// response is routed, after `before_send` completes and independently
4708 /// from how the returned request is eventually consumed.
4709 #[cfg(feature = "unstable_protocol_v2")]
4710 pub(crate) fn send_request_to_with_response_hook_after<
4711 Peer: Role,
4712 Req: JsonRpcRequest,
4713 BeforeSend: Future<Output = Result<(), crate::Error>> + Send + 'static,
4714 >(
4715 &self,
4716 peer: Peer,
4717 request: Req,
4718 before_send: BeforeSend,
4719 response_hook: impl FnOnce(&Req::Response) -> Result<(), crate::Error> + Send + 'static,
4720 ) -> SentRequest<Req::Response>
4721 where
4722 Counterpart: HasPeer<Peer>,
4723 {
4724 let hook: ResponseRouteHook = Box::new(move |method, value| {
4725 let response = Req::Response::from_value(method, value.clone())?;
4726 response_hook(&response)
4727 });
4728 self.send_request_to_with_options(
4729 peer,
4730 request,
4731 false,
4732 Some(RequestReadiness::new(before_send)),
4733 Some(hook),
4734 )
4735 }
4736
4737 /// Send an ordered request with readiness and valid-success hooks.
4738 #[cfg(feature = "unstable_protocol_v2")]
4739 pub(crate) fn send_ordered_request_to_with_response_hook_after<
4740 Peer: Role,
4741 Req: JsonRpcRequest,
4742 BeforeSend: Future<Output = Result<(), crate::Error>> + Send + 'static,
4743 >(
4744 &self,
4745 peer: Peer,
4746 request: Req,
4747 before_send: BeforeSend,
4748 response_hook: impl FnOnce(&Req::Response) -> Result<(), crate::Error> + Send + 'static,
4749 ) -> SentRequest<Req::Response>
4750 where
4751 Counterpart: HasPeer<Peer>,
4752 {
4753 let hook: ResponseRouteHook = Box::new(move |method, value| {
4754 let response = Req::Response::from_value(method, value.clone())?;
4755 response_hook(&response)
4756 });
4757 self.send_request_to_with_options(
4758 peer,
4759 request,
4760 true,
4761 Some(RequestReadiness::new(before_send)),
4762 Some(hook),
4763 )
4764 }
4765
4766 /// Send a request whose callback must run before later inbound messages.
4767 ///
4768 /// The ordering marker is installed before the request enters the outgoing
4769 /// queue, closing the race between a fast peer response and the immediate
4770 /// [`SentRequest::on_receiving_result`] call. Callers must consume the
4771 /// returned request with a callback-style method without yielding.
4772 pub(crate) fn send_ordered_request_to<Peer: Role, Req: JsonRpcRequest>(
4773 &self,
4774 peer: Peer,
4775 request: Req,
4776 ) -> SentRequest<Req::Response>
4777 where
4778 Counterpart: HasPeer<Peer>,
4779 {
4780 self.send_request_to_with_options(peer, request, true, None, None)
4781 }
4782
4783 /// Send an ordered request after `before_send` completes successfully.
4784 ///
4785 /// The ordering marker and readiness prerequisite are both registered
4786 /// before the request enters the outgoing queue. This is used by framework
4787 /// setup paths that must acknowledge local routing before the peer can
4788 /// observe the request.
4789 pub(crate) fn send_ordered_request_to_after<
4790 Peer: Role,
4791 Req: JsonRpcRequest,
4792 BeforeSend: Future<Output = Result<(), crate::Error>> + Send + 'static,
4793 >(
4794 &self,
4795 peer: Peer,
4796 request: Req,
4797 before_send: BeforeSend,
4798 ) -> SentRequest<Req::Response>
4799 where
4800 Counterpart: HasPeer<Peer>,
4801 {
4802 self.send_request_to_with_options(
4803 peer,
4804 request,
4805 true,
4806 Some(RequestReadiness::new(before_send)),
4807 None,
4808 )
4809 }
4810
4811 fn send_request_to_with_options<Peer: Role, Req: JsonRpcRequest>(
4812 &self,
4813 peer: Peer,
4814 request: Req,
4815 ordered: bool,
4816 readiness: Option<RequestReadiness>,
4817 response_route_hook: Option<ResponseRouteHook>,
4818 ) -> SentRequest<Req::Response>
4819 where
4820 Counterpart: HasPeer<Peer>,
4821 {
4822 self.prepare_request_to_with_options(peer, request, readiness, response_route_hook)
4823 .into_sent_request(ordered)
4824 }
4825
4826 fn prepare_request_to_with_options<Peer: Role, Req: JsonRpcRequest>(
4827 &self,
4828 peer: Peer,
4829 request: Req,
4830 readiness: Option<RequestReadiness>,
4831 response_route_hook: Option<ResponseRouteHook>,
4832 ) -> PreparedRequest<Req::Response>
4833 where
4834 Counterpart: HasPeer<Peer>,
4835 {
4836 let method = request.method().to_string();
4837 let id = RequestId::Str(uuid::Uuid::new_v4().to_string());
4838 let (response_tx, response_rx) = oneshot::channel();
4839 let response_ordering = ResponseOrdering::default();
4840 let role_id = peer.role_id();
4841 let remote_style = self.counterpart.remote_style(peer);
4842 let cancellation =
4843 SentRequestCancellation::new(self.message_tx.clone(), remote_style, id.clone());
4844 let pending_reply = PendingReply {
4845 method: method.clone(),
4846 role_id,
4847 sender: response_tx,
4848 cancellation_disarm: cancellation.disarm_handle(),
4849 ordering: response_ordering.clone(),
4850 response_route_hook,
4851 accepts_lower_initialize_version: false,
4852 };
4853 let message = if self.is_incoming_closing() {
4854 Err(incoming_transport_closed_error(&method))
4855 } else {
4856 request
4857 .to_untyped_message()
4858 .map(|untyped| OutgoingMessage::Request {
4859 id: id.clone(),
4860 method: method.clone(),
4861 untyped,
4862 remote_style,
4863 readiness,
4864 })
4865 .map_err(|error| {
4866 crate::util::internal_error(format!(
4867 "failed to create untyped request for `{method}`: {error}"
4868 ))
4869 })
4870 };
4871 let sent = SentRequest::new(
4872 id,
4873 method.clone(),
4874 self.task_tx.clone(),
4875 response_rx,
4876 cancellation,
4877 response_ordering,
4878 )
4879 .map(move |json| <Req::Response>::from_value(&method, json));
4880 PreparedRequest {
4881 sent,
4882 publication: RequestPublication {
4883 message,
4884 pending_reply,
4885 message_tx: self.message_tx.clone(),
4886 pending_replies: self.pending_replies.clone(),
4887 incoming_closed: self.incoming_closed.clone(),
4888 },
4889 }
4890 }
4891
4892 /// Send an outgoing notification to the default counterpart peer (no reply expected).
4893 ///
4894 /// Notifications are fire-and-forget messages that don't have IDs and don't expect responses.
4895 /// This method sends the notification immediately and returns.
4896 ///
4897 /// This is a convenience method that sends to the counterpart role `R`.
4898 /// For explicit control over the target peer, use [`send_notification_to`](Self::send_notification_to).
4899 ///
4900 /// ```no_run
4901 /// # use agent_client_protocol_test::*;
4902 /// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::Agent>) -> Result<(), agent_client_protocol::Error> {
4903 /// cx.send_notification(StatusUpdate {
4904 /// message: "Processing...".into(),
4905 /// })?;
4906 /// # Ok(())
4907 /// # }
4908 /// ```
4909 pub fn send_notification<N: JsonRpcNotification>(
4910 &self,
4911 notification: N,
4912 ) -> Result<(), crate::Error>
4913 where
4914 Counterpart: HasPeer<Counterpart>,
4915 {
4916 self.send_notification_to(self.counterpart.clone(), notification)
4917 }
4918
4919 /// Send an outgoing notification to a specific peer (no reply expected).
4920 ///
4921 /// The message will be transformed according to the [`HasPeer`](crate::role::HasPeer)
4922 /// implementation before being sent.
4923 pub fn send_notification_to<Peer: Role, N: JsonRpcNotification>(
4924 &self,
4925 peer: Peer,
4926 notification: N,
4927 ) -> Result<(), crate::Error>
4928 where
4929 Counterpart: HasPeer<Peer>,
4930 {
4931 let remote_style = self.counterpart.remote_style(peer);
4932 tracing::debug!(
4933 role = std::any::type_name::<Counterpart>(),
4934 peer = std::any::type_name::<Peer>(),
4935 notification_type = std::any::type_name::<N>(),
4936 ?remote_style,
4937 original_method = notification.method(),
4938 "send_notification_to"
4939 );
4940 let transformed = remote_style.transform_outgoing_message(notification)?;
4941 tracing::debug!(
4942 transformed_method = %transformed.method,
4943 "send_notification_to transformed"
4944 );
4945 send_raw_message(
4946 &self.message_tx,
4947 OutgoingMessage::Notification {
4948 untyped: transformed,
4949 },
4950 )
4951 }
4952
4953 /// Send a `$/cancel_request` notification for an arbitrary request ID to
4954 /// the default counterpart peer.
4955 ///
4956 /// Prefer [`SentRequest::cancel`] when you have the request handle: it
4957 /// already knows the correct peer, request ID, and proxy wrapping. Use this
4958 /// low-level method only when implementing custom routing with a request ID
4959 /// that is valid on this connection.
4960 pub fn send_cancel_request(
4961 &self,
4962 request_id: impl Into<crate::schema::v1::RequestId>,
4963 ) -> Result<(), crate::Error>
4964 where
4965 Counterpart: HasPeer<Counterpart>,
4966 {
4967 self.send_cancel_request_to(self.counterpart.clone(), request_id)
4968 }
4969
4970 /// Send a `$/cancel_request` notification for an arbitrary request ID to a
4971 /// specific peer.
4972 ///
4973 /// Prefer [`SentRequest::cancel`] when you have the request handle: it
4974 /// already knows the correct peer, request ID, and proxy wrapping. Use this
4975 /// low-level method only when implementing custom routing with a request ID
4976 /// that is valid on the target peer's connection.
4977 pub fn send_cancel_request_to<Peer: Role>(
4978 &self,
4979 peer: Peer,
4980 request_id: impl Into<crate::schema::v1::RequestId>,
4981 ) -> Result<(), crate::Error>
4982 where
4983 Counterpart: HasPeer<Peer>,
4984 {
4985 self.send_notification_to(
4986 peer,
4987 crate::schema::v1::CancelRequestNotification::new(request_id),
4988 )
4989 }
4990
4991 /// Register a dynamic message handler, used to intercept messages specific to a particular session
4992 /// or some similar modal thing.
4993 ///
4994 /// Dynamic message handlers run after the handlers registered on [`Builder`] and before the
4995 /// role's default handler. They receive messages that the builder handlers decline.
4996 ///
4997 /// The handler will stay registered until the returned registration guard is dropped.
4998 pub fn add_dynamic_handler(
4999 &self,
5000 handler: impl HandleDispatchFrom<Counterpart> + 'static,
5001 ) -> Result<DynamicHandlerGuard<Counterpart>, crate::Error> {
5002 let uuid = Uuid::new_v4();
5003 let active = Arc::new(AtomicBool::new(true));
5004 self.dynamic_handler_tx
5005 .unbounded_send(DynamicHandlerMessage::AddDynamicHandler(
5006 uuid,
5007 Box::new(GuardedDynamicHandler {
5008 active: active.clone(),
5009 handler,
5010 }),
5011 ))
5012 .map_err(crate::util::internal_error)?;
5013
5014 Ok(DynamicHandlerGuard::new(uuid, active, self.clone()))
5015 }
5016
5017 /// Wait until every dynamic-handler update queued before this call has
5018 /// been applied by the incoming protocol actor.
5019 pub(crate) fn dynamic_handler_barrier(&self) -> BoxFuture<'static, Result<(), crate::Error>> {
5020 let (acknowledgment_tx, acknowledgment_rx) = oneshot::channel();
5021 if let Err(error) =
5022 self.dynamic_handler_tx
5023 .unbounded_send(DynamicHandlerMessage::AcknowledgedBarrier(
5024 acknowledgment_tx,
5025 ))
5026 {
5027 return future::ready(Err(crate::Error::into_internal_error(error))).boxed();
5028 }
5029
5030 async move {
5031 acknowledgment_rx.await.map_err(|error| {
5032 crate::util::internal_error(format!(
5033 "dynamic-handler barrier was dropped before acknowledgment: {error}"
5034 ))
5035 })
5036 }
5037 .boxed()
5038 }
5039
5040 fn remove_dynamic_handler(&self, uuid: Uuid) {
5041 // Ignore errors
5042 drop(
5043 self.dynamic_handler_tx
5044 .unbounded_send(DynamicHandlerMessage::RemoveDynamicHandler(uuid)),
5045 );
5046 }
5047}
5048
5049struct GuardedDynamicHandler<Handler> {
5050 active: Arc<AtomicBool>,
5051 handler: Handler,
5052}
5053
5054impl<Counterpart, Handler> HandleDispatchFrom<Counterpart> for GuardedDynamicHandler<Handler>
5055where
5056 Counterpart: Role,
5057 Handler: HandleDispatchFrom<Counterpart>,
5058{
5059 async fn handle_dispatch_from(
5060 &mut self,
5061 message: Dispatch,
5062 connection: ConnectionTo<Counterpart>,
5063 ) -> Result<Handled<Dispatch>, crate::Error> {
5064 if !self.active.load(Ordering::Acquire) {
5065 return Ok(Handled::No {
5066 message,
5067 retry: false,
5068 });
5069 }
5070 self.handler.handle_dispatch_from(message, connection).await
5071 }
5072
5073 fn describe_chain(&self) -> impl Debug {
5074 self.handler.describe_chain()
5075 }
5076}
5077
5078/// A guard that keeps a dynamic message handler registered.
5079///
5080/// Dropping the guard immediately deactivates the handler and queues its
5081/// removal from the connection. Use [`detach`](Self::detach) to keep the
5082/// handler registered for the remaining lifetime of the connection.
5083#[must_use = "dropping this guard unregisters the dynamic handler"]
5084#[derive(Debug)]
5085pub struct DynamicHandlerGuard<R: Role> {
5086 uuid: Option<Uuid>,
5087 active: Arc<AtomicBool>,
5088 cx: ConnectionTo<R>,
5089 cleanup: Option<Arc<dyn DynamicHandlerCleanup>>,
5090}
5091
5092/// Private registration-local cleanup, independent of connection task admission.
5093pub(crate) trait DynamicHandlerCleanup: std::fmt::Debug + Send + Sync {
5094 fn close(&self);
5095 fn wait(&self) -> futures::future::BoxFuture<'static, ()>;
5096}
5097
5098impl<R: Role> DynamicHandlerGuard<R> {
5099 fn new(uuid: Uuid, active: Arc<AtomicBool>, cx: ConnectionTo<R>) -> Self {
5100 Self {
5101 uuid: Some(uuid),
5102 active,
5103 cx,
5104 cleanup: None,
5105 }
5106 }
5107
5108 #[cfg(feature = "unstable_mcp_over_acp")]
5109 pub(crate) fn with_cleanup(mut self, cleanup: Arc<dyn DynamicHandlerCleanup>) -> Self {
5110 self.cleanup = Some(cleanup);
5111 self
5112 }
5113
5114 pub(crate) fn cleanup(&self) -> Option<Arc<dyn DynamicHandlerCleanup>> {
5115 self.cleanup.clone()
5116 }
5117
5118 /// Keep the dynamic handler registered after this guard is dropped.
5119 ///
5120 /// The handler remains registered until the connection itself shuts down.
5121 /// Unlike leaking the guard, detaching does not retain an extra
5122 /// [`ConnectionTo`] handle.
5123 pub fn detach(mut self) {
5124 self.uuid = None;
5125 }
5126}
5127
5128impl<R: Role> Drop for DynamicHandlerGuard<R> {
5129 fn drop(&mut self) {
5130 if let Some(uuid) = self.uuid {
5131 self.active.store(false, Ordering::Release);
5132 if let Some(cleanup) = &self.cleanup {
5133 cleanup.close();
5134 }
5135 self.cx.remove_dynamic_handler(uuid);
5136 }
5137 }
5138}
5139
5140/// The context to respond to an incoming request.
5141///
5142/// This context is provided to request handlers and serves a dual role:
5143///
5144/// 1. **Respond to the request** - Use [`respond`](Self::respond) or
5145/// [`respond_with_result`](Self::respond_with_result) to send the response
5146/// 2. **Send other messages** - Use the [`ConnectionTo`] parameter passed to your
5147/// handler, which provides [`send_request`](`ConnectionTo::send_request`),
5148/// [`send_notification`](`ConnectionTo::send_notification`), and
5149/// [`spawn`](`ConnectionTo::spawn`)
5150///
5151/// # Example
5152///
5153/// ```no_run
5154/// # use agent_client_protocol_test::*;
5155/// # async fn example() -> Result<(), agent_client_protocol::Error> {
5156/// # let connection = mock_connection();
5157/// connection.on_receive_request(async |req: ProcessRequest, responder, cx| {
5158/// // Send a notification while processing
5159/// cx.send_notification(StatusUpdate {
5160/// message: "processing".into(),
5161/// })?;
5162///
5163/// // Do some work...
5164/// let result = process(&req.data)?;
5165///
5166/// // Respond to the request
5167/// responder.respond(ProcessResponse { result })
5168/// }, agent_client_protocol::on_receive_request!())
5169/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
5170/// # Ok(())
5171/// # }
5172/// ```
5173///
5174/// # Event Loop Considerations
5175///
5176/// Like all handlers, request handlers run on the event loop. Use
5177/// [`spawn`](ConnectionTo::spawn) for expensive operations to avoid blocking
5178/// the connection.
5179///
5180/// See the [Event Loop and Concurrency](Builder#event-loop-and-concurrency)
5181/// section for more details.
5182///
5183/// # Drop behavior
5184///
5185/// Dropping a responder for a request that arrived in a batch completes that
5186/// slot with an Internal Error, so one abandoned request cannot withhold valid
5187/// sibling responses forever. A responder for an individual request retains
5188/// the historical behavior: dropping it does not automatically send a reply.
5189#[must_use]
5190pub struct Responder<T: JsonRpcResponse = serde_json::Value> {
5191 /// The method of the request.
5192 method: String,
5193
5194 /// The `id` of the message we are replying to.
5195 id: RequestId,
5196
5197 /// Request-local cancellation state.
5198 cancellation: ResponderCancellation,
5199
5200 /// Whether this response is emitted on its own or collected into a batch.
5201 destination: ResponseDestination,
5202
5203 /// Function to send the response to its destination.
5204 ///
5205 /// For incoming requests: serializes to JSON and sends over the wire.
5206 /// For incoming responses: sends to the waiting oneshot channel.
5207 send_fn: Box<dyn FnOnce(Result<T, crate::Error>) -> Result<(), crate::Error> + Send>,
5208
5209 /// Completes an abandoned batch slot unless an explicit response disarms it.
5210 drop_guard: ResponderDropGuard,
5211}
5212
5213struct ResponderDropGuard {
5214 message_tx: OutgoingMessageTx,
5215 id: RequestId,
5216 method: String,
5217 destination: ResponseDestination,
5218 armed: bool,
5219}
5220
5221impl ResponderDropGuard {
5222 fn disarm(&mut self) {
5223 self.armed = false;
5224 }
5225}
5226
5227impl Drop for ResponderDropGuard {
5228 fn drop(&mut self) {
5229 if !self.armed || !self.destination.is_batch() {
5230 return;
5231 }
5232
5233 if let Err(error) = send_raw_message(
5234 &self.message_tx,
5235 OutgoingMessage::AbandonedBatchResponse {
5236 id: self.id.clone(),
5237 method: self.method.clone(),
5238 destination: self.destination.clone(),
5239 },
5240 ) {
5241 tracing::debug!(
5242 id = ?self.id,
5243 method = %self.method,
5244 error = ?crate::util::redacted_error(error.code),
5245 "could not complete abandoned JSON-RPC batch response slot"
5246 );
5247 }
5248 }
5249}
5250
5251impl<T: JsonRpcResponse> std::fmt::Debug for Responder<T> {
5252 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5253 f.debug_struct("Responder")
5254 .field("method", &self.method)
5255 .field("id", &self.id)
5256 .field("response_type", &std::any::type_name::<T>())
5257 .finish_non_exhaustive()
5258 }
5259}
5260
5261impl Responder<serde_json::Value> {
5262 /// Create a new request context for an incoming request.
5263 ///
5264 /// The response will be serialized to JSON and sent over the wire.
5265 fn new(
5266 message_tx: OutgoingMessageTx,
5267 method: String,
5268 id: RequestId,
5269 cancellation_registry: &RequestCancellationRegistry,
5270 destination: ResponseDestination,
5271 ) -> Self {
5272 let id_clone = id.clone();
5273 let method_clone = method.clone();
5274 let cancellation = cancellation_registry.register(&id);
5275 let send_destination = destination.clone();
5276 let drop_guard = ResponderDropGuard {
5277 message_tx: message_tx.clone(),
5278 id: id.clone(),
5279 method: method.clone(),
5280 destination: destination.clone(),
5281 armed: true,
5282 };
5283 Self {
5284 method,
5285 id,
5286 cancellation,
5287 destination,
5288 send_fn: Box::new(move |response: Result<serde_json::Value, crate::Error>| {
5289 send_raw_message(
5290 &message_tx,
5291 OutgoingMessage::Response {
5292 id: id_clone,
5293 method: method_clone,
5294 response,
5295 destination: send_destination,
5296 },
5297 )
5298 }),
5299 drop_guard,
5300 }
5301 }
5302
5303 /// Cast this request context to a different response type.
5304 ///
5305 /// The provided type `T` will be serialized to JSON before sending.
5306 pub fn cast<T: JsonRpcResponse>(self) -> Responder<T> {
5307 self.wrap_params(move |method, value| match value {
5308 Ok(value) => T::into_json(value, method),
5309 Err(e) => Err(e),
5310 })
5311 }
5312}
5313
5314impl<T: JsonRpcResponse> Responder<T> {
5315 /// Method of the incoming request
5316 #[must_use]
5317 pub fn method(&self) -> &str {
5318 &self.method
5319 }
5320
5321 /// ID of the incoming request.
5322 #[must_use]
5323 pub fn id(&self) -> &RequestId {
5324 &self.id
5325 }
5326
5327 /// Returns the cancellation marker for this request.
5328 ///
5329 /// The marker is set when the peer sends `$/cancel_request` for this
5330 /// request's JSON-RPC ID. Cancellation is cooperative: handlers should use
5331 /// the marker to stop long-running work and then decide whether to respond
5332 /// with [`Error::request_cancelled`] or partial data.
5333 ///
5334 /// [`Error::request_cancelled`]: crate::Error::request_cancelled
5335 #[must_use]
5336 pub fn cancellation(&self) -> RequestCancellation {
5337 self.cancellation.cancellation()
5338 }
5339
5340 /// Convert to a `Responder` that expects a JSON value
5341 /// and which checks (dynamically) that the JSON value it receives
5342 /// can be converted to `T`.
5343 pub fn erase_to_json(self) -> Responder<serde_json::Value> {
5344 self.wrap_params(|method, value| T::from_value(method, value?))
5345 }
5346
5347 /// Return a new Responder with a different method name.
5348 pub fn wrap_method(mut self, method: String) -> Responder<T> {
5349 self.drop_guard.method.clone_from(&method);
5350 Responder {
5351 method,
5352 id: self.id,
5353 cancellation: self.cancellation,
5354 destination: self.destination,
5355 send_fn: self.send_fn,
5356 drop_guard: self.drop_guard,
5357 }
5358 }
5359
5360 /// Return a new Responder that expects a response of type U.
5361 ///
5362 /// `wrap_fn` will be invoked with the method name and the result to transform
5363 /// type `U` into type `T` before sending.
5364 pub fn wrap_params<U: JsonRpcResponse>(
5365 self,
5366 wrap_fn: impl FnOnce(&str, Result<U, crate::Error>) -> Result<T, crate::Error> + Send + 'static,
5367 ) -> Responder<U> {
5368 let method = self.method.clone();
5369 Responder {
5370 method: self.method,
5371 id: self.id,
5372 cancellation: self.cancellation,
5373 destination: self.destination,
5374 send_fn: Box::new(move |input: Result<U, crate::Error>| {
5375 let t_value = wrap_fn(&method, input);
5376 (self.send_fn)(t_value)
5377 }),
5378 drop_guard: self.drop_guard,
5379 }
5380 }
5381
5382 /// Respond to the JSON-RPC request with either a value (`Ok`) or an error (`Err`).
5383 pub fn respond_with_result(
5384 mut self,
5385 response: Result<T, crate::Error>,
5386 ) -> Result<(), crate::Error> {
5387 tracing::debug!(id = ?self.id, "respond called");
5388 self.drop_guard.disarm();
5389 (self.send_fn)(response)
5390 }
5391
5392 /// Respond to the JSON-RPC request with a value.
5393 pub fn respond(self, response: T) -> Result<(), crate::Error> {
5394 self.respond_with_result(Ok(response))
5395 }
5396
5397 /// Respond to the JSON-RPC request with an internal error containing a message.
5398 pub fn respond_with_internal_error(self, message: impl ToString) -> Result<(), crate::Error> {
5399 self.respond_with_error(crate::util::internal_error(message))
5400 }
5401
5402 /// Respond to the JSON-RPC request with an error.
5403 pub fn respond_with_error(self, error: crate::Error) -> Result<(), crate::Error> {
5404 tracing::debug!(id = ?self.id, error = ?crate::util::redacted_error(error.code), "respond_with_error called");
5405 self.respond_with_result(Err(error))
5406 }
5407
5408 fn reply_target(&self) -> RequestReplyTarget {
5409 RequestReplyTarget {
5410 id: self.id.clone(),
5411 method: self.method.clone(),
5412 destination: self.destination.clone(),
5413 }
5414 }
5415}
5416
5417/// Context for handling an incoming JSON-RPC response.
5418///
5419/// This is the response-side counterpart to [`Responder`]. While `Responder` handles
5420/// incoming requests (where you send a response over the wire), `ResponseRouter` handles
5421/// incoming responses (where you route the response to a local task waiting for it).
5422///
5423/// Both are fundamentally "sinks" that push the message through a `send_fn`, but they
5424/// represent different points in the message lifecycle and carry different metadata.
5425///
5426/// # Drop Behavior
5427///
5428/// Dropping a `ResponseRouter` without routing the response (for example, from a
5429/// dispatch handler that claims a [`Dispatch::Response`]) discards the
5430/// response: the local awaiter observes the response as never received. The
5431/// request still counts as settled: routing a response this far disarms the
5432/// originating [`SentRequest`]'s drop-time auto-cancellation even if the router
5433/// is never invoked, since the peer has already answered.
5434#[must_use]
5435pub struct ResponseRouter<T: JsonRpcResponse = serde_json::Value> {
5436 /// The method of the original request.
5437 method: String,
5438
5439 /// The `id` of the original request.
5440 id: RequestId,
5441
5442 /// The RoleId to which the original request was sent
5443 /// (and hence from which the reply is expected).
5444 role_id: RoleId,
5445
5446 /// Function to send the response to the waiting task.
5447 send_fn: Box<dyn FnOnce(Result<T, crate::Error>) -> Result<(), crate::Error> + Send>,
5448
5449 /// Shared route used to deliver a dispatch-handler error to the same waiter.
5450 reply_target: ResponseReplyTarget,
5451}
5452
5453impl<T: JsonRpcResponse> std::fmt::Debug for ResponseRouter<T> {
5454 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5455 f.debug_struct("ResponseRouter")
5456 .field("method", &self.method)
5457 .field("id", &self.id)
5458 .field("response_type", &std::any::type_name::<T>())
5459 .finish_non_exhaustive()
5460 }
5461}
5462
5463impl ResponseRouter<serde_json::Value> {
5464 /// Create a new response context for routing a response to a local awaiter.
5465 ///
5466 /// When [`route_with_result`](Self::route_with_result) is called, the response is sent through the oneshot
5467 /// channel to the code that originally sent the request. If that receiver was
5468 /// dropped, the response is discarded because there is no local awaiter left.
5469 fn new(id: RequestId, pending_reply: PendingReply, dispatch: ResponseDispatch) -> Self {
5470 let PendingReply {
5471 method,
5472 role_id,
5473 sender,
5474 cancellation_disarm,
5475 ordering,
5476 response_route_hook,
5477 accepts_lower_initialize_version: _,
5478 } = pending_reply;
5479 let reply_target = ResponseReplyTarget {
5480 id: id.clone(),
5481 method: method.clone(),
5482 sender: Arc::new(Mutex::new(Some(sender))),
5483 ordering,
5484 dispatch,
5485 };
5486 let send_target = reply_target.clone();
5487 // A response for the request reached this router, so the request is
5488 // settled from the peer's perspective and a `$/cancel_request` could
5489 // only ever be redundant. Disarm immediately so handlers may retain
5490 // the router without leaving auto-cancellation armed.
5491 cancellation_disarm.disarm();
5492 let hook_method = method.clone();
5493 Self {
5494 method,
5495 id,
5496 role_id,
5497 send_fn: Box::new(move |response: Result<serde_json::Value, crate::Error>| {
5498 let response = match response {
5499 Ok(value) => match response_route_hook {
5500 Some(hook) => hook(&hook_method, &value).map(|()| value),
5501 None => Ok(value),
5502 },
5503 Err(error) => Err(error),
5504 };
5505 send_target.route(response);
5506 Ok(())
5507 }),
5508 reply_target,
5509 }
5510 }
5511
5512 /// Cast this response context to a different response type.
5513 ///
5514 /// The provided type `T` will be serialized to JSON before sending.
5515 pub fn cast<T: JsonRpcResponse>(self) -> ResponseRouter<T> {
5516 self.wrap_params(move |method, value| match value {
5517 Ok(value) => T::into_json(value, method),
5518 Err(e) => Err(e),
5519 })
5520 }
5521}
5522
5523impl<T: JsonRpcResponse> ResponseRouter<T> {
5524 /// Method of the original request
5525 #[must_use]
5526 pub fn method(&self) -> &str {
5527 &self.method
5528 }
5529
5530 /// ID of the original request.
5531 #[must_use]
5532 pub fn id(&self) -> &RequestId {
5533 &self.id
5534 }
5535
5536 /// The peer to which the original request was sent.
5537 ///
5538 /// This is the peer from which we expect to receive the response.
5539 #[must_use]
5540 pub fn role_id(&self) -> RoleId {
5541 self.role_id.clone()
5542 }
5543
5544 /// Convert to a `ResponseRouter` that expects a JSON value
5545 /// and which checks (dynamically) that the JSON value it receives
5546 /// can be converted to `T`.
5547 pub fn erase_to_json(self) -> ResponseRouter<serde_json::Value> {
5548 self.wrap_params(|method, value| T::from_value(method, value?))
5549 }
5550
5551 /// Return a new ResponseRouter that expects a response of type U.
5552 ///
5553 /// `wrap_fn` will be invoked with the method name and the result to transform
5554 /// type `U` into type `T` before sending.
5555 fn wrap_params<U: JsonRpcResponse>(
5556 self,
5557 wrap_fn: impl FnOnce(&str, Result<U, crate::Error>) -> Result<T, crate::Error> + Send + 'static,
5558 ) -> ResponseRouter<U> {
5559 let method = self.method.clone();
5560 ResponseRouter {
5561 method: self.method,
5562 id: self.id,
5563 role_id: self.role_id,
5564 send_fn: Box::new(move |input: Result<U, crate::Error>| {
5565 let t_value = wrap_fn(&method, input);
5566 (self.send_fn)(t_value)
5567 }),
5568 reply_target: self.reply_target,
5569 }
5570 }
5571
5572 /// Route the response result to the waiting task.
5573 pub fn route_with_result(self, response: Result<T, crate::Error>) -> Result<(), crate::Error> {
5574 tracing::debug!(id = ?self.id, "response routed to awaiter");
5575 (self.send_fn)(response)
5576 }
5577
5578 /// Route a successful response value to the waiting task.
5579 pub fn route(self, response: T) -> Result<(), crate::Error> {
5580 self.route_with_result(Ok(response))
5581 }
5582
5583 /// Route an internal error to the waiting task.
5584 pub fn route_with_internal_error(self, message: impl ToString) -> Result<(), crate::Error> {
5585 self.route_with_error(crate::util::internal_error(message))
5586 }
5587
5588 /// Route an error response to the waiting task.
5589 pub fn route_with_error(self, error: crate::Error) -> Result<(), crate::Error> {
5590 tracing::debug!(id = ?self.id, error = ?crate::util::redacted_error(error.code), "error routed to awaiter");
5591 self.route_with_result(Err(error))
5592 }
5593}
5594
5595/// Common bounds for any JSON-RPC message.
5596///
5597/// # Derive Macro
5598///
5599/// For simple message types, you can use the `JsonRpcRequest` or `JsonRpcNotification` derive macros
5600/// which will implement both `JsonRpcMessage` and the respective trait. See [`JsonRpcRequest`] and
5601/// [`JsonRpcNotification`] for examples.
5602pub trait JsonRpcMessage: 'static + Debug + Sized + Send + Clone {
5603 /// Check if this message type matches the given method name.
5604 fn matches_method(method: &str) -> bool;
5605
5606 /// The method name for the message.
5607 fn method(&self) -> &str;
5608
5609 /// Convert this message into an untyped message.
5610 fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error>;
5611
5612 /// Parse this type from a method name and parameters.
5613 ///
5614 /// Returns an error if the method doesn't match or deserialization fails.
5615 /// Callers should use `matches_method` first to check if this type handles the method.
5616 fn parse_message(method: &str, params: &impl Serialize) -> Result<Self, crate::Error>;
5617}
5618
5619/// Defines the "payload" of a successful response to a JSON-RPC request.
5620///
5621/// # Derive Macro
5622///
5623/// Use `#[derive(JsonRpcResponse)]` to automatically implement this trait:
5624///
5625/// ```ignore
5626/// use agent_client_protocol::JsonRpcResponse;
5627/// use serde::{Serialize, Deserialize};
5628///
5629/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcResponse)]
5630/// struct HelloResponse {
5631/// greeting: String,
5632/// }
5633/// ```
5634pub trait JsonRpcResponse: 'static + Debug + Sized + Send + Clone {
5635 /// Convert this message into a JSON value.
5636 fn into_json(self, method: &str) -> Result<serde_json::Value, crate::Error>;
5637
5638 /// Parse a JSON value into the response type.
5639 fn from_value(method: &str, value: serde_json::Value) -> Result<Self, crate::Error>;
5640}
5641
5642impl JsonRpcResponse for serde_json::Value {
5643 fn from_value(_method: &str, value: serde_json::Value) -> Result<Self, crate::Error> {
5644 Ok(value)
5645 }
5646
5647 fn into_json(self, _method: &str) -> Result<serde_json::Value, crate::Error> {
5648 Ok(self)
5649 }
5650}
5651
5652/// A struct that represents a notification (JSON-RPC message that does not expect a response).
5653///
5654/// # Derive Macro
5655///
5656/// Use `#[derive(JsonRpcNotification)]` to automatically implement both `JsonRpcMessage` and `JsonRpcNotification`:
5657///
5658/// ```ignore
5659/// use agent_client_protocol::JsonRpcNotification;
5660/// use serde::{Serialize, Deserialize};
5661///
5662/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcNotification)]
5663/// #[notification(method = "_ping")]
5664/// struct PingNotification {
5665/// timestamp: u64,
5666/// }
5667/// ```
5668pub trait JsonRpcNotification: JsonRpcMessage {}
5669
5670/// A struct that represents a request (JSON-RPC message expecting a response).
5671///
5672/// # Derive Macro
5673///
5674/// Use `#[derive(JsonRpcRequest)]` to automatically implement both `JsonRpcMessage` and `JsonRpcRequest`:
5675///
5676/// ```ignore
5677/// use agent_client_protocol::{JsonRpcRequest, JsonRpcResponse};
5678/// use serde::{Serialize, Deserialize};
5679///
5680/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcRequest)]
5681/// #[request(method = "_hello", response = HelloResponse)]
5682/// struct HelloRequest {
5683/// name: String,
5684/// }
5685///
5686/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcResponse)]
5687/// struct HelloResponse {
5688/// greeting: String,
5689/// }
5690/// ```
5691pub trait JsonRpcRequest: JsonRpcMessage {
5692 /// The type of data expected in response.
5693 type Response: JsonRpcResponse;
5694}
5695
5696/// An incoming request, notification, or response being dispatched through handlers.
5697/// Requests include the context used to answer them; responses include the context
5698/// used to route them to the local requester.
5699///
5700/// Type parameters allow specifying the concrete request and notification types.
5701/// By default, both are `UntypedMessage` for dynamic dispatch.
5702/// The request context's response type matches the request's response type.
5703#[derive(Debug)]
5704pub enum Dispatch<Req: JsonRpcRequest = UntypedMessage, Notif: JsonRpcNotification = UntypedMessage>
5705{
5706 /// Incoming request and the context where the response should be sent.
5707 Request(Req, Responder<Req::Response>),
5708
5709 /// Incoming notification.
5710 Notification(Notif),
5711
5712 /// Incoming response to a request we sent.
5713 ///
5714 /// The first field is the response result (success or error from the remote).
5715 /// The second field is the context for forwarding the response to its destination
5716 /// (typically a waiting oneshot channel).
5717 Response(
5718 Result<Req::Response, crate::Error>,
5719 ResponseRouter<Req::Response>,
5720 ),
5721}
5722
5723impl<Req: JsonRpcRequest, Notif: JsonRpcNotification> Dispatch<Req, Notif> {
5724 /// Map the request and notification types to new types.
5725 ///
5726 /// Note: Response variants are passed through unchanged since they don't
5727 /// contain a parseable message payload.
5728 pub fn map<Req1, Notif1>(
5729 self,
5730 map_request: impl FnOnce(Req, Responder<Req::Response>) -> (Req1, Responder<Req1::Response>),
5731 map_notification: impl FnOnce(Notif) -> Notif1,
5732 ) -> Dispatch<Req1, Notif1>
5733 where
5734 Req1: JsonRpcRequest<Response = Req::Response>,
5735 Notif1: JsonRpcNotification,
5736 {
5737 match self {
5738 Dispatch::Request(request, responder) => {
5739 let (new_request, new_responder) = map_request(request, responder);
5740 Dispatch::Request(new_request, new_responder)
5741 }
5742 Dispatch::Notification(notification) => {
5743 let new_notification = map_notification(notification);
5744 Dispatch::Notification(new_notification)
5745 }
5746 Dispatch::Response(result, router) => Dispatch::Response(result, router),
5747 }
5748 }
5749
5750 /// Convert the message in self to an untyped message.
5751 ///
5752 /// Note: Response variants don't have an untyped message representation.
5753 /// This returns an error for Response variants.
5754 pub fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
5755 match self {
5756 Dispatch::Request(request, _) => request.to_untyped_message(),
5757 Dispatch::Notification(notification) => notification.to_untyped_message(),
5758 Dispatch::Response(_, _) => Err(crate::util::internal_error(
5759 "Response variant has no untyped message representation",
5760 )),
5761 }
5762 }
5763
5764 /// Convert self to an untyped message context.
5765 ///
5766 /// Note: Response variants cannot be converted. This returns an error for Response variants.
5767 pub fn into_untyped_dispatch(self) -> Result<Dispatch, crate::Error> {
5768 match self {
5769 Dispatch::Request(request, responder) => Ok(Dispatch::Request(
5770 request.to_untyped_message()?,
5771 responder.erase_to_json(),
5772 )),
5773 Dispatch::Notification(notification) => {
5774 Ok(Dispatch::Notification(notification.to_untyped_message()?))
5775 }
5776 Dispatch::Response(_, _) => Err(crate::util::internal_error(
5777 "cannot convert Response variant to untyped message context",
5778 )),
5779 }
5780 }
5781
5782 /// Returns the request ID if this is a request or response, None if notification.
5783 pub fn id(&self) -> Option<&RequestId> {
5784 match self {
5785 Dispatch::Request(_, cx) => Some(cx.id()),
5786 Dispatch::Notification(_) => None,
5787 Dispatch::Response(_, cx) => Some(cx.id()),
5788 }
5789 }
5790
5791 fn handler_error_target(&self) -> Option<HandlerErrorTarget> {
5792 match self {
5793 Dispatch::Request(_, responder) => {
5794 Some(HandlerErrorTarget::Request(responder.reply_target()))
5795 }
5796 Dispatch::Notification(_) => None,
5797 Dispatch::Response(_, router) => {
5798 Some(HandlerErrorTarget::Response(router.reply_target.clone()))
5799 }
5800 }
5801 }
5802
5803 /// Returns the method of the message.
5804 ///
5805 /// For requests and notifications, this is the method from the message payload.
5806 /// For responses, this is the method of the original request.
5807 pub fn method(&self) -> &str {
5808 match self {
5809 Dispatch::Request(msg, _) => msg.method(),
5810 Dispatch::Notification(msg) => msg.method(),
5811 Dispatch::Response(_, cx) => cx.method(),
5812 }
5813 }
5814}
5815
5816impl Dispatch {
5817 /// Attempts to parse `self` into a typed message context.
5818 ///
5819 /// # Returns
5820 ///
5821 /// * `Ok(Ok(typed))` if this dispatch matches the requested type for its variant
5822 /// * `Ok(Err(self))` if it does not match the requested type for its variant
5823 /// * `Err` if its method matches the requested type but parsing fails
5824 #[tracing::instrument(skip(self), fields(Request = ?std::any::type_name::<Req>(), Notif = ?std::any::type_name::<Notif>()), level = "trace", ret)]
5825 pub(crate) fn into_typed_dispatch<Req: JsonRpcRequest, Notif: JsonRpcNotification>(
5826 self,
5827 ) -> Result<Result<Dispatch<Req, Notif>, Dispatch>, crate::Error> {
5828 tracing::debug!(
5829 method = self.method(),
5830 id = ?self.id(),
5831 "into_typed_dispatch"
5832 );
5833 match self {
5834 Dispatch::Request(message, responder) => {
5835 if Req::matches_method(&message.method) {
5836 match Req::parse_message(&message.method, &message.params) {
5837 Ok(req) => {
5838 tracing::trace!("parsed ok");
5839 Ok(Ok(Dispatch::Request(req, responder.cast())))
5840 }
5841 Err(err) => {
5842 tracing::trace!(err = ?crate::util::redacted_error(err.code), "parse error");
5843 Err(err)
5844 }
5845 }
5846 } else {
5847 tracing::trace!("method doesn't match");
5848 Ok(Err(Dispatch::Request(message, responder)))
5849 }
5850 }
5851
5852 Dispatch::Notification(message) => {
5853 if Notif::matches_method(&message.method) {
5854 match Notif::parse_message(&message.method, &message.params) {
5855 Ok(notif) => {
5856 tracing::trace!("parse ok");
5857 Ok(Ok(Dispatch::Notification(notif)))
5858 }
5859 Err(err) => {
5860 tracing::trace!(err = ?crate::util::redacted_error(err.code), "parse error");
5861 Err(err)
5862 }
5863 }
5864 } else {
5865 tracing::trace!("method doesn't match");
5866 Ok(Err(Dispatch::Notification(message)))
5867 }
5868 }
5869
5870 Dispatch::Response(result, cx) => {
5871 let method = cx.method();
5872 if Req::matches_method(method) {
5873 // Parse the response result
5874 let typed_result = match result {
5875 Ok(value) => {
5876 match <Req::Response as JsonRpcResponse>::from_value(method, value) {
5877 Ok(parsed) => {
5878 tracing::trace!("parse ok");
5879 Ok(parsed)
5880 }
5881 Err(err) => {
5882 tracing::trace!(err = ?crate::util::redacted_error(err.code), "parse error");
5883 return Err(err);
5884 }
5885 }
5886 }
5887 Err(err) => {
5888 tracing::trace!("error, passthrough");
5889 Err(err)
5890 }
5891 };
5892 Ok(Ok(Dispatch::Response(typed_result, cx.cast())))
5893 } else {
5894 tracing::trace!("method doesn't match");
5895 Ok(Err(Dispatch::Response(result, cx)))
5896 }
5897 }
5898 }
5899 }
5900
5901 /// True if this message has a field with the given name.
5902 ///
5903 /// Returns `false` for Response variants.
5904 #[must_use]
5905 pub fn has_field(&self, field_name: &str) -> bool {
5906 self.message()
5907 .and_then(|m| m.params().get(field_name))
5908 .is_some()
5909 }
5910
5911 /// Returns true if this message has a session-id field.
5912 ///
5913 /// Returns `false` for Response variants.
5914 pub(crate) fn has_session_id(&self) -> bool {
5915 self.has_field("sessionId")
5916 }
5917
5918 /// Extract the ACP session-id from this message (if any).
5919 ///
5920 /// Returns `Ok(None)` for Response variants.
5921 pub(crate) fn get_session_id(&self) -> Result<Option<SessionId>, crate::Error> {
5922 let Some(message) = self.message() else {
5923 return Ok(None);
5924 };
5925 let Some(value) = message.params().get("sessionId") else {
5926 return Ok(None);
5927 };
5928 let session_id = serde_json::from_value(value.clone())?;
5929 Ok(Some(session_id))
5930 }
5931
5932 /// Try to parse this as a notification of the given type.
5933 ///
5934 /// # Returns
5935 ///
5936 /// * `Ok(Ok(typed))` if this is a notification of the requested type
5937 /// * `Ok(Err(self))` if this is not a matching notification
5938 /// * `Err` if its method matches the requested type but parsing fails
5939 pub fn into_notification<N: JsonRpcNotification>(
5940 self,
5941 ) -> Result<Result<N, Dispatch>, crate::Error> {
5942 match self {
5943 Dispatch::Notification(msg) => {
5944 if !N::matches_method(&msg.method) {
5945 return Ok(Err(Dispatch::Notification(msg)));
5946 }
5947 match N::parse_message(&msg.method, &msg.params) {
5948 Ok(n) => Ok(Ok(n)),
5949 Err(err) => Err(err),
5950 }
5951 }
5952 Dispatch::Request(..) | Dispatch::Response(..) => Ok(Err(self)),
5953 }
5954 }
5955
5956 /// Try to parse this as a request of the given type.
5957 ///
5958 /// # Returns
5959 ///
5960 /// * `Ok(Ok(typed))` if this is a request of the requested type
5961 /// * `Ok(Err(self))` if this is not a matching request
5962 /// * `Err` if its method matches the requested type but parsing fails
5963 pub fn into_request<Req: JsonRpcRequest>(
5964 self,
5965 ) -> Result<Result<(Req, Responder<Req::Response>), Dispatch>, crate::Error> {
5966 match self {
5967 Dispatch::Request(msg, responder) => {
5968 if !Req::matches_method(&msg.method) {
5969 return Ok(Err(Dispatch::Request(msg, responder)));
5970 }
5971 match Req::parse_message(&msg.method, &msg.params) {
5972 Ok(req) => Ok(Ok((req, responder.cast()))),
5973 Err(err) => Err(err),
5974 }
5975 }
5976 Dispatch::Notification(..) | Dispatch::Response(..) => Ok(Err(self)),
5977 }
5978 }
5979}
5980
5981impl<M: JsonRpcRequest + JsonRpcNotification> Dispatch<M, M> {
5982 /// Returns the message payload for requests and notifications.
5983 ///
5984 /// Returns `None` for Response variants since they don't contain a message payload.
5985 pub fn message(&self) -> Option<&M> {
5986 match self {
5987 Dispatch::Request(msg, _) | Dispatch::Notification(msg) => Some(msg),
5988 Dispatch::Response(_, _) => None,
5989 }
5990 }
5991
5992 /// Map the request/notification message.
5993 ///
5994 /// Response variants pass through unchanged.
5995 pub(crate) fn try_map_message(
5996 self,
5997 map_message: impl FnOnce(M) -> Result<M, crate::Error>,
5998 ) -> Result<Dispatch<M, M>, crate::Error> {
5999 match self {
6000 Dispatch::Request(request, cx) => Ok(Dispatch::Request(map_message(request)?, cx)),
6001 Dispatch::Notification(notification) => {
6002 Ok(Dispatch::<M, M>::Notification(map_message(notification)?))
6003 }
6004 Dispatch::Response(result, cx) => Ok(Dispatch::Response(result, cx)),
6005 }
6006 }
6007}
6008
6009/// An incoming JSON message without any typing. Can be a request or a notification.
6010#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
6011pub struct UntypedMessage {
6012 /// The JSON-RPC method name
6013 pub method: String,
6014 /// The JSON-RPC parameters as a raw JSON value
6015 pub params: serde_json::Value,
6016}
6017
6018impl UntypedMessage {
6019 /// Returns an untyped message with the given method and parameters.
6020 pub fn new(method: &str, params: impl Serialize) -> Result<Self, crate::Error> {
6021 let params = serde_json::to_value(params)?;
6022 Ok(Self {
6023 method: method.to_string(),
6024 params,
6025 })
6026 }
6027
6028 /// Returns the method name
6029 #[must_use]
6030 pub fn method(&self) -> &str {
6031 &self.method
6032 }
6033
6034 /// Returns the parameters as a JSON value
6035 #[must_use]
6036 pub fn params(&self) -> &serde_json::Value {
6037 &self.params
6038 }
6039
6040 /// Consumes this message and returns the method and params
6041 #[must_use]
6042 pub fn into_parts(self) -> (String, serde_json::Value) {
6043 (self.method, self.params)
6044 }
6045
6046 /// Convert `self` to a raw JSON-RPC message.
6047 pub(crate) fn into_raw_jsonrpc_message(
6048 self,
6049 id: Option<RequestId>,
6050 ) -> Result<RawJsonRpcMessage, crate::Error> {
6051 let Self { method, params } = self;
6052 match id {
6053 Some(id) => RawJsonRpcMessage::request(method, params, id),
6054 None => RawJsonRpcMessage::notification(method, params),
6055 }
6056 }
6057}
6058
6059impl JsonRpcMessage for UntypedMessage {
6060 fn matches_method(_method: &str) -> bool {
6061 // UntypedMessage matches any method - it's the untyped fallback
6062 true
6063 }
6064
6065 fn method(&self) -> &str {
6066 &self.method
6067 }
6068
6069 fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
6070 Ok(self.clone())
6071 }
6072
6073 fn parse_message(method: &str, params: &impl Serialize) -> Result<Self, crate::Error> {
6074 UntypedMessage::new(method, params)
6075 }
6076}
6077
6078impl JsonRpcRequest for UntypedMessage {
6079 type Response = serde_json::Value;
6080}
6081
6082impl JsonRpcNotification for UntypedMessage {}
6083
6084/// Represents a pending response of type `R` from an outgoing request.
6085///
6086/// Returned by [`ConnectionTo::send_request`], this type provides explicit response-consumption
6087/// modes. The API is intentionally designed to make it difficult to accidentally wait for a
6088/// response inside the dispatch loop.
6089///
6090/// # Anti-Footgun Design
6091///
6092/// You cannot directly `.await` a `SentRequest`. Instead, you must choose how to handle
6093/// the response:
6094///
6095/// ## Option 1: Register an Ordered Callback (Safe in Handlers)
6096///
6097/// Calling [`on_receiving_result`](Self::on_receiving_result) registers the callback and returns
6098/// immediately. When ordered consumption is selected before the response is routed during its
6099/// original dispatch, the loop waits for the callback to complete before processing the next
6100/// message:
6101///
6102/// ```no_run
6103/// # use agent_client_protocol_test::*;
6104/// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
6105/// cx.send_request(MyRequest {})
6106/// .on_receiving_result(async |result| {
6107/// match result {
6108/// Ok(response) => {
6109/// // Handle successful response
6110/// Ok(())
6111/// }
6112/// Err(error) => {
6113/// // Handle error
6114/// Err(error)
6115/// }
6116/// }
6117/// })?;
6118/// # Ok(())
6119/// # }
6120/// ```
6121///
6122/// ## Option 2: Wait Outside the Dispatch Loop
6123///
6124/// Use [`block_task`](Self::block_task) only when the current task already runs outside the
6125/// dispatch loop—for example, in the foreground future passed to `connect_with` or in a task
6126/// created with [`ConnectionTo::spawn`]. Never await it in a handler:
6127///
6128/// ```no_run
6129/// # use agent_client_protocol_test::*;
6130/// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
6131/// // ✅ Safe: Spawned task runs concurrently
6132/// cx.spawn({
6133/// let cx = cx.clone();
6134/// async move {
6135/// let response = cx.send_request(MyRequest {})
6136/// .block_task()
6137/// .await?;
6138/// // Process response...
6139/// Ok(())
6140/// }
6141/// })?;
6142/// # Ok(())
6143/// # }
6144/// ```
6145///
6146/// ```no_run
6147/// # use agent_client_protocol_test::*;
6148/// # async fn example() -> Result<(), agent_client_protocol::Error> {
6149/// # let connection = mock_connection();
6150/// // ❌ NEVER do this in a handler - blocks the event loop!
6151/// connection.on_receive_request(async |req: MyRequest, responder, cx| {
6152/// let response = cx.send_request(MyRequest {})
6153/// .block_task() // This will deadlock!
6154/// .await?;
6155/// responder.respond(response)
6156/// }, agent_client_protocol::on_receive_request!())
6157/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
6158/// # Ok(())
6159/// # }
6160/// ```
6161///
6162/// # Why This Design?
6163///
6164/// If you block the event loop while waiting for a response, the connection cannot process
6165/// the incoming response message, creating a deadlock. This API design prevents that footgun
6166/// by making blocking explicit and encouraging non-blocking patterns.
6167///
6168/// # Drop Behavior
6169///
6170/// By default, dropping a `SentRequest` before the SDK has received the
6171/// response sends a `$/cancel_request` notification asking the peer to cancel
6172/// the request, then discards the response when it arrives. Requests whose
6173/// eventual response should be ignored, but which should keep running on the
6174/// peer, should use [`detach`](Self::detach) instead.
6175///
6176/// # Incoming Transport EOF
6177///
6178/// If the incoming transport reaches clean EOF before the response arrives, every
6179/// consumption mode receives an error with the message `Incoming transport
6180/// closed` and data containing
6181/// `{"reason":"incoming_transport_closed","method":"..."}`. Requests made
6182/// after incoming EOF fail immediately with the same error. Use
6183/// [`is_incoming_transport_closed`] to identify it.
6184#[must_use = "dropping a SentRequest asks the peer to cancel the request and \
6185 discards the response; consume it with `block_task`, \
6186 `on_receiving_result`, `forward_response_to`, or `detach`"]
6187pub struct SentRequest<T> {
6188 id: RequestId,
6189 method: String,
6190 task_tx: TaskTx,
6191 response_rx: oneshot::Receiver<ResponsePayload>,
6192 to_result: Box<dyn FnOnce(serde_json::Value) -> Result<T, crate::Error> + Send>,
6193 cancellation: SentRequestCancellation,
6194 response_ordering: ResponseOrdering,
6195 /// Cancellation markers of other (incoming) requests whose cancellation
6196 /// should be forwarded to this request. See
6197 /// [`forward_cancellation_from`](Self::forward_cancellation_from).
6198 cancellation_sources: Vec<RequestCancellation>,
6199}
6200
6201/// A request that has not been published to its connection.
6202///
6203/// Created by [`ConnectionTo::prepare_request`] or
6204/// [`ConnectionTo::prepare_request_to`]. Preparation serializes the request but
6205/// does not register a pending reply or enqueue outgoing traffic. Dropping this
6206/// value sends neither the request nor a cancellation notification.
6207///
6208/// A consuming method publishes the request synchronously:
6209///
6210/// - [`on_receiving_result`](Self::on_receiving_result),
6211/// [`on_receiving_ok_result`](Self::on_receiving_ok_result), and
6212/// [`forward_response_to`](Self::forward_response_to) register ordered response
6213/// handling before publication. When a peer response is routed during its
6214/// original dispatch, later inbound messages wait for that handling to finish.
6215/// - [`block_task`](Self::block_task) publishes immediately and returns an
6216/// unordered response future. Publication does not wait for its first poll.
6217/// - [`detach`](Self::detach) publishes immediately and discards the response.
6218///
6219/// Ordered callbacks must do bounded work and must not await later inbound
6220/// traffic on the same connection. EOF failures and responses routed through a
6221/// retained [`ResponseRouter`] after their original dispatch have no ordering
6222/// barrier. See [`crate::concepts::ordering`].
6223///
6224/// # Errors
6225///
6226/// Preparation and publication failures are delivered to the selected response
6227/// consumer. Callback-style methods return an error if their task cannot be
6228/// registered; in that case the request is not published. [`detach`](Self::detach)
6229/// returns preparation or publication errors directly because it has no response
6230/// consumer. A callback returning an error terminates the connection.
6231#[must_use = "a prepared request is not sent until consumed with `block_task`, \
6232 `on_receiving_result`, `forward_response_to`, or `detach`"]
6233pub struct PreparedRequest<T> {
6234 sent: SentRequest<T>,
6235 publication: RequestPublication,
6236}
6237
6238struct RequestPublication {
6239 message: Result<OutgoingMessage, crate::Error>,
6240 pending_reply: PendingReply,
6241 message_tx: OutgoingMessageTx,
6242 pending_replies: PendingRepliesRegistrar,
6243 incoming_closed: IncomingClosed,
6244}
6245
6246impl RequestPublication {
6247 fn publish(self) -> Result<(), crate::Error> {
6248 let message = if self.incoming_closed.is_closing() {
6249 Err(incoming_transport_closed_error(&self.pending_reply.method))
6250 } else {
6251 self.message
6252 };
6253 let message = match message {
6254 Ok(message) => message,
6255 Err(error) => {
6256 self.pending_reply.fail(error.clone());
6257 return Err(error);
6258 }
6259 };
6260 let OutgoingMessage::Request { id, method, .. } = &message else {
6261 unreachable!();
6262 };
6263 let id = id.clone();
6264 let method = method.clone();
6265 let cancellation_disarm = self.pending_reply.cancellation_disarm.clone();
6266 // Register before enqueueing so incoming EOF can fail every observable
6267 // request before close callbacks begin. The outgoing actor checks that
6268 // the registration still exists before sending the request.
6269 self.pending_replies
6270 .subscribe(id.clone(), self.pending_reply, &self.incoming_closed)?;
6271 if self.message_tx.unbounded_send(message).is_err() {
6272 let error = if self.incoming_closed.is_closing() {
6273 incoming_transport_closed_error(&method)
6274 } else {
6275 crate::util::internal_error(format!("failed to send outgoing request `{method}`"))
6276 };
6277 if let Some(pending_reply) = self.pending_replies.remove(&id) {
6278 pending_reply.fail(error.clone());
6279 }
6280 return Err(error);
6281 }
6282 // An escaped cancellation handle must not enqueue cancellation before
6283 // the request. A fast response or EOF may already have disarmed it.
6284 cancellation_disarm.arm();
6285 Ok(())
6286 }
6287}
6288
6289impl<T: Debug> Debug for PreparedRequest<T> {
6290 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6291 formatter
6292 .debug_struct("PreparedRequest")
6293 .field("request", &self.sent)
6294 .finish_non_exhaustive()
6295 }
6296}
6297
6298impl<T> PreparedRequest<T> {
6299 /// The ID reserved for this request, which has not been sent yet.
6300 #[must_use]
6301 pub fn id(&self) -> &RequestId {
6302 self.sent.id()
6303 }
6304
6305 /// The method of the prepared request.
6306 #[must_use]
6307 pub fn method(&self) -> &str {
6308 self.sent.method()
6309 }
6310
6311 /// Retain explicit cancellation control without publishing this request.
6312 ///
6313 /// The handle can outlive consumption of this request by an ordered callback
6314 /// or response future. Calling it before publication is a no-op, not a
6315 /// cancellation to apply when the request is later published. Dropping the
6316 /// handle does not cancel. See [`RequestCancellationHandle`].
6317 #[must_use]
6318 pub fn cancellation_handle(&self) -> RequestCancellationHandle {
6319 self.sent.cancellation_handle()
6320 }
6321
6322 /// Map a successful response without publishing the request.
6323 ///
6324 /// The mapper has the same contract as [`SentRequest::map`].
6325 pub fn map<U>(
6326 self,
6327 map_fn: impl FnOnce(T) -> Result<U, crate::Error> + 'static + Send,
6328 ) -> PreparedRequest<U>
6329 where
6330 T: 'static,
6331 {
6332 PreparedRequest {
6333 sent: self.sent.map(map_fn),
6334 publication: self.publication,
6335 }
6336 }
6337
6338 /// Register a cancellation source without publishing the request.
6339 ///
6340 /// After publication, cancellation is forwarded while awaiting the response,
6341 /// as described by [`SentRequest::forward_cancellation_from`].
6342 pub fn forward_cancellation_from(mut self, source: RequestCancellation) -> Self {
6343 self.sent = self.sent.forward_cancellation_from(source);
6344 self
6345 }
6346
6347 /// Publish now and return an unordered future for the response.
6348 ///
6349 /// The request is enqueued during this call, not when the future is first
6350 /// polled. Dropping that future asks the peer to cancel a still-outstanding
6351 /// request. Await it only outside the dispatch loop; awaiting it in an
6352 /// incoming handler deadlocks just like [`SentRequest::block_task`].
6353 ///
6354 /// # Errors
6355 ///
6356 /// The returned future delivers preparation, publication, and response errors.
6357 pub fn block_task(self) -> impl Future<Output = Result<T, crate::Error>> {
6358 self.into_sent_request(false).block_task()
6359 }
6360
6361 /// Publish now and discard the eventual response without cancelling.
6362 ///
6363 /// # Errors
6364 ///
6365 /// Returns immediate preparation or enqueue failures. Later local
6366 /// transformation errors and peer response errors are discarded along with
6367 /// successful responses. Transport failures still propagate through the
6368 /// connection future. Retained [`RequestCancellationHandle`] values can
6369 /// still explicitly cancel the request while it remains pending.
6370 pub fn detach(self) -> Result<(), crate::Error> {
6371 let result = self.publication.publish();
6372 self.sent.detach();
6373 result
6374 }
6375
6376 /// Register an ordered callback, then publish the request.
6377 ///
6378 /// Ordering is selected before publication, even when the connection runs
6379 /// concurrently. See [`PreparedRequest`] for barrier limits and deadlock risks.
6380 ///
6381 /// # Errors
6382 ///
6383 /// Returns an error if the callback task cannot be registered, without
6384 /// publishing the request. Preparation and publication errors are delivered
6385 /// to the callback. Returning an error from the callback ends the connection.
6386 #[track_caller]
6387 pub fn on_receiving_result<F>(
6388 self,
6389 task: impl FnOnce(Result<T, crate::Error>) -> F + 'static + Send,
6390 ) -> Result<(), crate::Error>
6391 where
6392 T: 'static,
6393 F: Future<Output = Result<(), crate::Error>> + 'static + Send,
6394 {
6395 self.consume_with(move |response| match response {
6396 Ok(result) => Either::Left(task(result)),
6397 Err(error) => Either::Right(future::ready(Err(error))),
6398 })
6399 }
6400
6401 /// Register an ordered success callback, then publish the request.
6402 ///
6403 /// Errors are forwarded to `responder`, as with
6404 /// [`SentRequest::on_receiving_ok_result`].
6405 ///
6406 /// # Errors
6407 ///
6408 /// Returns a task-registration error without publishing the request.
6409 /// Preparation, publication, and response errors are forwarded to `responder`.
6410 /// Returning an error from the callback ends the connection.
6411 #[track_caller]
6412 pub fn on_receiving_ok_result<F>(
6413 self,
6414 responder: Responder<T>,
6415 task: impl FnOnce(T, Responder<T>) -> F + 'static + Send,
6416 ) -> Result<(), crate::Error>
6417 where
6418 T: JsonRpcResponse,
6419 F: Future<Output = Result<(), crate::Error>> + 'static + Send,
6420 {
6421 self.on_receiving_result(async move |result| match result {
6422 Ok(value) => task(value, responder).await,
6423 Err(error) => responder.respond_with_error(error),
6424 })
6425 }
6426
6427 /// Register ordered response forwarding, then publish the request.
6428 ///
6429 /// Cancellation and response errors propagate as with
6430 /// [`SentRequest::forward_response_to`].
6431 ///
6432 /// # Errors
6433 ///
6434 /// Returns a task-registration error without publishing the request.
6435 /// Preparation, publication, and response errors are forwarded to `responder`.
6436 #[track_caller]
6437 pub fn forward_response_to(self, responder: Responder<T>) -> Result<(), crate::Error>
6438 where
6439 T: JsonRpcResponse,
6440 {
6441 self.forward_cancellation_from(responder.cancellation())
6442 .consume_with(async move |response| {
6443 responder.respond_with_result(response.unwrap_or_else(Err))
6444 })
6445 }
6446
6447 fn into_sent_request(self, ordered: bool) -> SentRequest<T> {
6448 if ordered {
6449 self.sent.response_ordering.mark_ordered();
6450 }
6451 // Publication errors also settle the response channel.
6452 drop(self.publication.publish());
6453 self.sent
6454 }
6455
6456 #[track_caller]
6457 fn consume_with<F>(
6458 self,
6459 handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
6460 ) -> Result<(), crate::Error>
6461 where
6462 T: 'static,
6463 F: Future<Output = Result<(), crate::Error>> + 'static + Send,
6464 {
6465 let published_tx = self.register_consumer(handle)?;
6466 drop(self.publication.publish());
6467 // Keep the cancellation guard here until publication completes. Even
6468 // destruction of the registered task cannot cancel before enqueueing.
6469 drop(published_tx.send(self.sent));
6470 Ok(())
6471 }
6472
6473 #[track_caller]
6474 fn register_consumer<F>(
6475 &self,
6476 handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
6477 ) -> Result<oneshot::Sender<SentRequest<T>>, crate::Error>
6478 where
6479 T: 'static,
6480 F: Future<Output = Result<(), crate::Error>> + 'static + Send,
6481 {
6482 self.sent.response_ordering.mark_ordered();
6483 let (published_tx, published_rx) = oneshot::channel::<SentRequest<T>>();
6484 Task::new(Location::caller(), async move {
6485 match published_rx.await {
6486 Ok(sent) => sent.handle_response(handle).await,
6487 // Publication was abandoned before the consumer took ownership.
6488 Err(_) => Ok(()),
6489 }
6490 })
6491 .spawn(&self.sent.task_tx)?;
6492 Ok(published_tx)
6493 }
6494}
6495
6496#[derive(Clone, Debug)]
6497pub(crate) struct SentRequestCancellationDisarm {
6498 state: Arc<AtomicU8>,
6499}
6500
6501#[repr(u8)]
6502enum OutgoingCancellationState {
6503 Unpublished,
6504 Armed,
6505 Disarmed,
6506}
6507
6508impl SentRequestCancellationDisarm {
6509 fn new() -> Self {
6510 Self {
6511 state: Arc::new(AtomicU8::new(OutgoingCancellationState::Unpublished as u8)),
6512 }
6513 }
6514
6515 fn disarm(&self) {
6516 self.state
6517 .store(OutgoingCancellationState::Disarmed as u8, Ordering::Release);
6518 }
6519
6520 fn arm(&self) -> bool {
6521 self.state
6522 .compare_exchange(
6523 OutgoingCancellationState::Unpublished as u8,
6524 OutgoingCancellationState::Armed as u8,
6525 Ordering::AcqRel,
6526 Ordering::Acquire,
6527 )
6528 .is_ok()
6529 }
6530
6531 fn take_armed(&self) -> bool {
6532 self.state
6533 .compare_exchange(
6534 OutgoingCancellationState::Armed as u8,
6535 OutgoingCancellationState::Disarmed as u8,
6536 Ordering::AcqRel,
6537 Ordering::Acquire,
6538 )
6539 .is_ok()
6540 }
6541
6542 fn is_armed(&self) -> bool {
6543 self.state.load(Ordering::Acquire) == OutgoingCancellationState::Armed as u8
6544 }
6545}
6546
6547/// Explicit cancellation control for one outgoing request.
6548///
6549/// Obtain this handle from [`PreparedRequest::cancellation_handle`] or
6550/// [`SentRequest::cancellation_handle`] before consuming the request. It retains
6551/// neither the response consumer nor application callback, so cancellation can
6552/// be requested without discarding the eventual response. It does not keep the
6553/// connection driver or response consumer alive, or abort local callback work.
6554///
6555/// Clones share the request's cancellation state with [`SentRequest::cancel`],
6556/// forwarded cancellation, and request-drop automatic cancellation. At most one
6557/// cancellation notification is attempted, with the original peer and proxy
6558/// wrapping. Dropping this handle neither cancels the request nor disables its
6559/// automatic cancellation.
6560///
6561/// Cancellation before publication is a no-op and is not remembered for later
6562/// publication. A call racing publication may also be a no-op; call after the
6563/// publishing method returns to target a pending request. Once the SDK routes a
6564/// response or fails the request, subsequent cancellation calls are no-ops,
6565/// even if its callback has not yet run. Detaching the request suppresses only
6566/// automatic cancellation; retained handles can still explicitly cancel it.
6567///
6568/// This controls outgoing requests, unlike [`RequestCancellation`], which
6569/// observes a peer's cancellation of an incoming request.
6570#[derive(Clone)]
6571pub struct RequestCancellationHandle {
6572 message_tx: OutgoingMessageTx,
6573 remote_style: crate::role::RemoteStyle,
6574 request_id: RequestId,
6575 disarm: SentRequestCancellationDisarm,
6576}
6577
6578impl RequestCancellationHandle {
6579 /// Ask the peer to cancel this request without discarding its response.
6580 ///
6581 /// Cancellation is cooperative: the peer may respond normally or with a
6582 /// cancellation error. Repeated calls, including calls through other clones
6583 /// or the original request, return `Ok(())` without sending another
6584 /// notification. Calls before publication or after settlement are no-ops.
6585 /// An attempt begun before settlement may still enqueue afterward.
6586 ///
6587 /// `Ok(())` means this call encountered no immediate error, not that a
6588 /// notification was sent or the peer stopped work. This method does not
6589 /// wait for transmission or acknowledgment.
6590 ///
6591 /// # Errors
6592 ///
6593 /// Only the call that attempts to send reports serialization or enqueue
6594 /// failure. A failed send is not retried by later cancellation calls.
6595 pub fn cancel(&self) -> Result<(), crate::Error> {
6596 if !self.disarm.take_armed() {
6597 return Ok(());
6598 }
6599
6600 // Build the notification lazily: most requests are never cancelled,
6601 // so this avoids serializing a notification per outgoing request.
6602 let untyped = self.remote_style.transform_outgoing_message(
6603 crate::schema::v1::CancelRequestNotification::new(self.request_id.clone()),
6604 )?;
6605
6606 send_raw_message(&self.message_tx, OutgoingMessage::Notification { untyped })
6607 }
6608}
6609
6610impl Debug for RequestCancellationHandle {
6611 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6612 formatter
6613 .debug_struct("RequestCancellationHandle")
6614 .field("request_id", &self.request_id)
6615 .field("remote_style", &self.remote_style)
6616 .field("armed", &self.disarm.is_armed())
6617 .finish_non_exhaustive()
6618 }
6619}
6620
6621#[derive(Debug)]
6622struct SentRequestCancellation {
6623 handle: RequestCancellationHandle,
6624 cancel_on_drop: bool,
6625}
6626
6627impl SentRequestCancellation {
6628 fn new(
6629 message_tx: OutgoingMessageTx,
6630 remote_style: crate::role::RemoteStyle,
6631 request_id: RequestId,
6632 ) -> Self {
6633 Self {
6634 handle: RequestCancellationHandle {
6635 message_tx,
6636 remote_style,
6637 request_id,
6638 disarm: SentRequestCancellationDisarm::new(),
6639 },
6640 cancel_on_drop: true,
6641 }
6642 }
6643
6644 fn disarm(&self) {
6645 self.handle.disarm.disarm();
6646 }
6647
6648 fn disarm_handle(&self) -> SentRequestCancellationDisarm {
6649 self.handle.disarm.clone()
6650 }
6651
6652 fn send(&self) -> Result<(), crate::Error> {
6653 self.handle.cancel()
6654 }
6655}
6656
6657impl Drop for SentRequestCancellation {
6658 fn drop(&mut self) {
6659 if !self.cancel_on_drop {
6660 return;
6661 }
6662 if let Err(error) = self.send() {
6663 tracing::debug!(
6664 error = ?crate::util::redacted_error(error.code),
6665 "failed to auto-cancel dropped request"
6666 );
6667 }
6668 }
6669}
6670
6671/// Await the response payload for an outgoing request, watching `sources` for
6672/// cancellation of the upstream requests it was registered with.
6673///
6674/// When any source reports cancellation, a `$/cancel_request` is forwarded to
6675/// the outgoing request (at most once, shared with [`SentRequest::cancel`] and
6676/// drop-time auto-cancellation), and the response is *still* awaited: the peer
6677/// always answers, with normal data or a cancellation error.
6678///
6679/// Watching is deliberately bounded by response arrival so that completed
6680/// requests do not leak waiters on markers that will never fire.
6681async fn await_response_forwarding_cancellation(
6682 response_rx: oneshot::Receiver<ResponsePayload>,
6683 cancellation: &SentRequestCancellation,
6684 sources: &[RequestCancellation],
6685) -> Result<ResponsePayload, oneshot::Canceled> {
6686 // Failing to forward the cancellation must not abort the wait: the
6687 // response (normal data or a cancellation error) may still arrive and
6688 // must still be processed.
6689 let forward_cancellation = || {
6690 if let Err(error) = cancellation.send() {
6691 tracing::debug!(
6692 error = ?crate::util::redacted_error(error.code),
6693 "failed to forward cancellation to downstream request"
6694 );
6695 }
6696 };
6697
6698 let response = if sources.is_empty() {
6699 response_rx.await
6700 } else if sources.iter().any(RequestCancellation::is_cancelled) {
6701 forward_cancellation();
6702 response_rx.await
6703 } else {
6704 let cancelled = sources.iter().map(|source| source.state.signal_rx.clone());
6705 match future::select(future::select_all(cancelled), response_rx).await {
6706 Either::Left((_, response_rx)) => {
6707 forward_cancellation();
6708 response_rx.await
6709 }
6710 Either::Right((response, _)) => response,
6711 }
6712 };
6713
6714 cancellation.disarm();
6715 response
6716}
6717
6718impl<T: Debug> Debug for SentRequest<T> {
6719 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6720 let mut debug = f.debug_struct("SentRequest");
6721 debug
6722 .field("id", &self.id)
6723 .field("method", &self.method)
6724 .field("task_tx", &self.task_tx)
6725 .field("response_rx", &self.response_rx);
6726 debug
6727 .field("cancellation", &self.cancellation)
6728 .field("cancellation_sources", &self.cancellation_sources);
6729 debug.finish_non_exhaustive()
6730 }
6731}
6732
6733impl SentRequest<serde_json::Value> {
6734 fn new(
6735 id: RequestId,
6736 method: String,
6737 task_tx: mpsc::UnboundedSender<Task>,
6738 response_rx: oneshot::Receiver<ResponsePayload>,
6739 cancellation: SentRequestCancellation,
6740 response_ordering: ResponseOrdering,
6741 ) -> Self {
6742 Self {
6743 id,
6744 method,
6745 response_rx,
6746 task_tx,
6747 to_result: Box::new(Ok),
6748 cancellation,
6749 response_ordering,
6750 cancellation_sources: Vec::new(),
6751 }
6752 }
6753}
6754
6755impl<T> SentRequest<T> {
6756 /// Detach this request handle without waiting for its response.
6757 ///
6758 /// The response will be discarded when it arrives. This also disables the
6759 /// drop-time automatic cancellation described in
6760 /// [Drop Behavior](Self#drop-behavior), so use it for requests whose
6761 /// eventual response should be ignored, but which should keep running on
6762 /// the peer. The peer is still expected to answer the JSON-RPC request
6763 /// eventually; use a notification instead when no response is expected at
6764 /// all.
6765 ///
6766 /// To ask the peer to stop the request, call `cancel` instead, or drop the
6767 /// handle while automatic cancellation is enabled. A retained
6768 /// [`RequestCancellationHandle`] can still explicitly cancel the detached
6769 /// request until the SDK receives its response or fails it.
6770 pub fn detach(mut self) {
6771 self.cancellation.cancel_on_drop = false;
6772 }
6773
6774 /// Send a `$/cancel_request` notification for this outgoing request.
6775 ///
6776 /// This uses the same peer and message wrapping that were used to send the
6777 /// original request, so it is the preferred way to cancel a [`SentRequest`]
6778 /// when the request handle is still available.
6779 ///
6780 /// At most one cancellation attempt is made per request, shared with
6781 /// retained handles, forwarded cancellation, and automatic cancellation
6782 /// described in [Drop Behavior](Self#drop-behavior). Later calls return
6783 /// `Ok(())` without another attempt, including when the first attempt failed.
6784 /// Once the SDK has routed the response, a new call is a no-op; an attempt
6785 /// begun before settlement may still enqueue afterward.
6786 ///
6787 /// `Ok(())` means this call encountered no immediate error, not that a
6788 /// notification was sent or the peer stopped work.
6789 ///
6790 /// Errors are only reported by the call that attempts to send the
6791 /// notification.
6792 pub fn cancel(&self) -> Result<(), crate::Error> {
6793 self.cancellation.send()
6794 }
6795
6796 /// Obtain a handle that remains usable after this request is consumed.
6797 ///
6798 /// The handle shares the once-only cancellation state used by
6799 /// [`cancel`](Self::cancel), response routing, and automatic request-drop
6800 /// cancellation, but has no cancel-on-drop behavior of its own.
6801 #[must_use]
6802 pub fn cancellation_handle(&self) -> RequestCancellationHandle {
6803 self.cancellation.handle.clone()
6804 }
6805
6806 /// Forward cancellation of another request to this one.
6807 ///
6808 /// When the request that `source` belongs to is cancelled by its peer,
6809 /// a `$/cancel_request` for *this* request is sent to its peer, using the
6810 /// same wrapping as the original request. The response is still awaited
6811 /// and delivered as usual (normal data or a cancellation error), so this
6812 /// composes with [`block_task`](Self::block_task) and
6813 /// [`on_receiving_result`](Self::on_receiving_result).
6814 ///
6815 /// This is the building block for proxies that forward a request with
6816 /// custom logic instead of [`forward_response_to`](Self::forward_response_to)
6817 /// (which wires this up automatically from its responder). Without it,
6818 /// custom forwarding *absorbs* cancellation: the upstream marker is still
6819 /// set, but nothing is sent downstream.
6820 ///
6821 /// ```
6822 /// # use agent_client_protocol::{ConnectionTo, Error, Responder, UntypedRole};
6823 /// # use agent_client_protocol_test::{MyRequest, MyResponse};
6824 /// # async fn example(request: MyRequest, responder: Responder<MyResponse>, backend: ConnectionTo<UntypedRole>) -> Result<(), Error> {
6825 /// backend
6826 /// .send_request(request)
6827 /// .forward_cancellation_from(responder.cancellation())
6828 /// .on_receiving_result(async move |result| {
6829 /// // Custom result handling, e.g. bookkeeping or rewriting.
6830 /// responder.respond_with_result(result)
6831 /// })?;
6832 /// # Ok(())
6833 /// # }
6834 /// ```
6835 ///
6836 /// May be called multiple times; cancellation of any registered source
6837 /// triggers the forwarding (at most one `$/cancel_request` is ever sent
6838 /// per request). Sources are observed while the response is being
6839 /// awaited — that is, once the handle is consumed with
6840 /// [`block_task`](Self::block_task),
6841 /// [`on_receiving_result`](Self::on_receiving_result), or
6842 /// [`forward_response_to`](Self::forward_response_to); a source that was
6843 /// already cancelled by then is honored immediately.
6844 pub fn forward_cancellation_from(mut self, source: RequestCancellation) -> Self {
6845 self.cancellation_sources.push(source);
6846 self
6847 }
6848}
6849
6850impl<T> SentRequest<T> {
6851 /// The id of the outgoing request.
6852 #[must_use]
6853 pub fn id(&self) -> &RequestId {
6854 &self.id
6855 }
6856
6857 /// The method of the request this is in response to.
6858 #[must_use]
6859 pub fn method(&self) -> &str {
6860 &self.method
6861 }
6862
6863 /// Map a successful JSON-RPC response into an application type.
6864 ///
6865 /// The mapped type does not need to implement [`JsonRpcResponse`]. The
6866 /// mapper runs at most once and may consume captured state. JSON-RPC error
6867 /// responses bypass the mapper. The mapped type may carry a non-`'static`
6868 /// lifetime when it is consumed with [`block_task`](Self::block_task);
6869 /// callback-style consumption still requires a `'static` mapped type
6870 /// because its work is spawned onto the connection.
6871 pub fn map<U>(
6872 self,
6873 map_fn: impl FnOnce(T) -> Result<U, crate::Error> + 'static + Send,
6874 ) -> SentRequest<U>
6875 where
6876 T: 'static,
6877 {
6878 SentRequest {
6879 id: self.id,
6880 method: self.method,
6881 response_rx: self.response_rx,
6882 task_tx: self.task_tx,
6883 to_result: Box::new(move |value| map_fn((self.to_result)(value)?)),
6884 cancellation: self.cancellation,
6885 response_ordering: self.response_ordering,
6886 cancellation_sources: self.cancellation_sources,
6887 }
6888 }
6889
6890 /// Forward the response (success or error) to a request context when it arrives.
6891 ///
6892 /// This is a convenience method for proxying messages between connections. When the
6893 /// response arrives, it will be automatically sent to the provided request context,
6894 /// whether it's a successful response or an error.
6895 ///
6896 /// # Example: Proxying requests
6897 ///
6898 /// ```
6899 /// # use agent_client_protocol::UntypedRole;
6900 /// # use agent_client_protocol::{Builder, ConnectionTo};
6901 /// # use agent_client_protocol_test::*;
6902 /// # async fn example(cx: ConnectionTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
6903 /// // Set up backend connection builder
6904 /// let backend = UntypedRole.builder()
6905 /// .on_receive_request(async |req: MyRequest, responder, cx| {
6906 /// responder.respond(MyResponse { status: "ok".into() })
6907 /// }, agent_client_protocol::on_receive_request!());
6908 ///
6909 /// // Spawn backend and get a context to send to it
6910 /// let backend_connection = cx.spawn_connection(backend, MockTransport)?;
6911 ///
6912 /// // Set up proxy that forwards requests to backend
6913 /// UntypedRole.builder()
6914 /// .on_receive_request({
6915 /// let backend_connection = backend_connection.clone();
6916 /// async move |req: MyRequest, responder, cx| {
6917 /// // Forward the request to backend and proxy the response back
6918 /// backend_connection.send_request(req)
6919 /// .forward_response_to(responder)?;
6920 /// Ok(())
6921 /// }
6922 /// }, agent_client_protocol::on_receive_request!());
6923 /// # Ok(())
6924 /// # }
6925 /// ```
6926 ///
6927 /// # Type Safety
6928 ///
6929 /// The request context's response type must match the request's response type,
6930 /// ensuring type-safe message forwarding.
6931 ///
6932 /// # When to Use
6933 ///
6934 /// Use this when:
6935 /// - You're implementing a proxy or gateway pattern
6936 /// - You want to forward responses without processing them
6937 /// - The response types match between the outgoing request and incoming request
6938 ///
6939 /// This is equivalent to calling `on_receiving_result` and manually forwarding
6940 /// the result, with two proxy-specific additions:
6941 ///
6942 /// - If the pending response cannot be delivered, the incoming request is
6943 /// answered with an internal error instead of being left unanswered.
6944 /// Known clean incoming EOF is delivered like any other response
6945 /// error; an unexpected response-channel loss is forwarded as an outer
6946 /// consumption error.
6947 /// - When the peer cancels the incoming request, the cancellation is
6948 /// forwarded to the outgoing request, and the downstream response
6949 /// (normal data or a cancellation error) is still forwarded back. This is
6950 /// equivalent to registering the responder's marker with
6951 /// `forward_cancellation_from`.
6952 #[track_caller]
6953 pub fn forward_response_to(self, responder: Responder<T>) -> Result<(), crate::Error>
6954 where
6955 T: JsonRpcResponse,
6956 {
6957 let this = self.forward_cancellation_from(responder.cancellation());
6958
6959 this.consume_with(async move |response| {
6960 // An unexpected response-channel loss (outer `Err`) is forwarded
6961 // as an error: the incoming request must not be left unanswered.
6962 responder.respond_with_result(response.unwrap_or_else(Err))
6963 })
6964 }
6965
6966 /// Spawn the response-consumption task shared by
6967 /// [`on_receiving_result`](Self::on_receiving_result) and
6968 /// [`forward_response_to`](Self::forward_response_to).
6969 ///
6970 /// The task awaits the response (forwarding cancellation from registered
6971 /// sources while waiting, converts the payload, and invokes `handle` with
6972 /// the typed result (`Ok(Result<T, _>)`). The dispatch loop's ack, if any,
6973 /// is sent after `handle` completes.
6974 ///
6975 /// Clean incoming EOF is delivered as `Ok(Err(error))`, just like
6976 /// a peer response error, so callback-style consumers still run. If the
6977 /// response channel disappears for another reason, `handle` receives an
6978 /// outer `Err` describing that unexpected loss; there is no ack then.
6979 #[track_caller]
6980 fn consume_with<F>(
6981 self,
6982 handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
6983 ) -> Result<(), crate::Error>
6984 where
6985 T: 'static,
6986 F: Future<Output = Result<(), crate::Error>> + 'static + Send,
6987 {
6988 self.response_ordering.mark_ordered();
6989 let task_tx = self.task_tx.clone();
6990 Task::new(Location::caller(), self.handle_response(handle)).spawn(&task_tx)
6991 }
6992
6993 fn handle_response<F>(
6994 self,
6995 handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
6996 ) -> impl Future<Output = Result<(), crate::Error>> + Send
6997 where
6998 T: 'static,
6999 F: Future<Output = Result<(), crate::Error>> + 'static + Send,
7000 {
7001 let method = self.method;
7002 let response_rx = self.response_rx;
7003 let to_result = self.to_result;
7004 let cancellation = self.cancellation;
7005 let cancellation_sources = self.cancellation_sources;
7006 async move {
7007 let response = await_response_forwarding_cancellation(
7008 response_rx,
7009 &cancellation,
7010 &cancellation_sources,
7011 )
7012 .await;
7013
7014 match response {
7015 Ok(ResponsePayload { result, ack_tx }) => {
7016 // Convert the result using to_result for Ok values
7017 let typed_result = match result {
7018 Ok(json_value) => to_result(json_value),
7019 Err(err) => Err(err),
7020 };
7021
7022 let outcome = handle(Ok(typed_result)).await;
7023
7024 // Ack AFTER the handler completes - this is the key
7025 // difference from block_task. The dispatch loop waits for
7026 // this ack.
7027 if let Some(tx) = ack_tx {
7028 let _ = tx.send(());
7029 }
7030
7031 outcome
7032 }
7033 Err(err) => {
7034 handle(Err(crate::util::internal_error(format!(
7035 "response to `{method}` never received: {err}"
7036 ))))
7037 .await
7038 }
7039 }
7040 }
7041 }
7042
7043 /// Block the current task until the response is received.
7044 ///
7045 /// **Warning:** This method blocks the current async task. It is safe only when that task
7046 /// already runs outside the dispatch loop, such as the foreground future passed to
7047 /// `connect_with` or a task created with [`ConnectionTo::spawn`]. Using it directly in a
7048 /// handler callback will deadlock the connection.
7049 ///
7050 /// # Safe Usage (outside the dispatch loop)
7051 ///
7052 /// ```no_run
7053 /// # use agent_client_protocol_test::*;
7054 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
7055 /// # let connection = mock_connection();
7056 /// connection.on_receive_request(async |req: MyRequest, responder, cx| {
7057 /// // Spawn a task to handle the request
7058 /// cx.spawn({
7059 /// let connection = cx.clone();
7060 /// async move {
7061 /// // Safe: We're in a spawned task, not blocking the event loop
7062 /// let response = connection.send_request(OtherRequest {})
7063 /// .block_task()
7064 /// .await?;
7065 ///
7066 /// // Process the response...
7067 /// Ok(())
7068 /// }
7069 /// })?;
7070 ///
7071 /// // Respond immediately
7072 /// responder.respond(MyResponse { status: "ok".into() })
7073 /// }, agent_client_protocol::on_receive_request!())
7074 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
7075 /// # Ok(())
7076 /// # }
7077 /// ```
7078 ///
7079 /// # Unsafe Usage (in handlers - will deadlock!)
7080 ///
7081 /// ```no_run
7082 /// # use agent_client_protocol_test::*;
7083 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
7084 /// # let connection = mock_connection();
7085 /// connection.on_receive_request(async |req: MyRequest, responder, cx| {
7086 /// // ❌ DEADLOCK: Handler blocks event loop, which can't process the response
7087 /// let response = cx.send_request(OtherRequest {})
7088 /// .block_task()
7089 /// .await?;
7090 ///
7091 /// responder.respond(MyResponse { status: response.value })
7092 /// }, agent_client_protocol::on_receive_request!())
7093 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
7094 /// # Ok(())
7095 /// # }
7096 /// ```
7097 ///
7098 /// # When to Use
7099 ///
7100 /// Use this method when:
7101 /// - Your current task already runs outside the dispatch loop
7102 /// - You need the response value to proceed with your logic
7103 /// - Linear control flow is more natural than callbacks
7104 ///
7105 /// For handler callbacks, use [`on_receiving_result`](Self::on_receiving_result) instead.
7106 pub async fn block_task(self) -> Result<T, crate::Error> {
7107 let response = await_response_forwarding_cancellation(
7108 self.response_rx,
7109 &self.cancellation,
7110 &self.cancellation_sources,
7111 )
7112 .await;
7113
7114 match response {
7115 Ok(ResponsePayload {
7116 result: Ok(json_value),
7117 ack_tx,
7118 }) => {
7119 // Blocking consumers ack before converting or returning the
7120 // value, so dispatch can continue while the caller processes it.
7121 if let Some(tx) = ack_tx {
7122 let _ = tx.send(());
7123 }
7124 match (self.to_result)(json_value) {
7125 Ok(value) => Ok(value),
7126 Err(err) => Err(err),
7127 }
7128 }
7129 Ok(ResponsePayload {
7130 result: Err(err),
7131 ack_tx,
7132 }) => {
7133 if let Some(tx) = ack_tx {
7134 let _ = tx.send(());
7135 }
7136 Err(err)
7137 }
7138 Err(err) => Err(crate::util::internal_error(format!(
7139 "response to `{}` never received: {}",
7140 self.method, err
7141 ))),
7142 }
7143 }
7144
7145 /// Block the current task and transform the typed result before releasing
7146 /// the ordered-response barrier.
7147 ///
7148 /// Framework lifecycle code uses this when success transfers local state
7149 /// to the returned value while an error must drop that state before later
7150 /// messages from the same transport frame are dispatched. The synchronous
7151 /// transform must not wait for additional connection traffic.
7152 pub(crate) async fn block_task_with_ordered_result<U>(
7153 self,
7154 transform: impl FnOnce(Result<T, crate::Error>) -> Result<U, crate::Error>,
7155 ) -> Result<U, crate::Error> {
7156 let response = await_response_forwarding_cancellation(
7157 self.response_rx,
7158 &self.cancellation,
7159 &self.cancellation_sources,
7160 )
7161 .await;
7162
7163 let (result, ack_tx) = match response {
7164 Ok(ResponsePayload { result, ack_tx }) => {
7165 let typed_result = match result {
7166 Ok(json_value) => (self.to_result)(json_value),
7167 Err(error) => Err(error),
7168 };
7169 (typed_result, ack_tx)
7170 }
7171 Err(error) => (
7172 Err(crate::util::internal_error(format!(
7173 "response to `{}` never received: {error}",
7174 self.method
7175 ))),
7176 None,
7177 ),
7178 };
7179
7180 let outcome = transform(result);
7181 if let Some(acknowledgment) = ack_tx {
7182 let _ = acknowledgment.send(());
7183 }
7184 outcome
7185 }
7186
7187 /// Schedule an async task to run when a successful response is received.
7188 ///
7189 /// This is a convenience wrapper around [`on_receiving_result`](Self::on_receiving_result)
7190 /// for the common pattern of forwarding errors to a request context while only processing
7191 /// successful responses.
7192 ///
7193 /// # Behavior
7194 ///
7195 /// - If the response is `Ok(value)`, your task receives the value and the request context
7196 /// - If the response is `Err(error)`, the error is automatically sent to `responder`
7197 /// and your task is not called
7198 ///
7199 /// # Example: Chaining requests
7200 ///
7201 /// ```no_run
7202 /// # use agent_client_protocol_test::*;
7203 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
7204 /// # let connection = mock_connection();
7205 /// connection.on_receive_request(async |req: ValidateRequest, responder, cx| {
7206 /// // Send initial request
7207 /// cx.send_request(ValidateRequest { data: req.data.clone() })
7208 /// .on_receiving_ok_result(responder, async |validation, responder| {
7209 /// // Only runs if validation succeeded
7210 /// if validation.is_valid {
7211 /// // Respond to original request
7212 /// responder.respond(ValidateResponse { is_valid: true, error: None })
7213 /// } else {
7214 /// responder.respond_with_error(agent_client_protocol::util::internal_error("validation failed"))
7215 /// }
7216 /// })?;
7217 ///
7218 /// Ok(())
7219 /// }, agent_client_protocol::on_receive_request!())
7220 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
7221 /// # Ok(())
7222 /// # }
7223 /// ```
7224 ///
7225 /// # Ordering
7226 ///
7227 /// Like [`on_receiving_result`](Self::on_receiving_result), response handling holds the
7228 /// dispatch loop through callback completion when ordered consumption is selected before a
7229 /// peer response is routed during its original dispatch. Pending-request failures delivered
7230 /// without an incoming response and delayed routes do not carry that barrier. The callback
7231 /// must not await later inbound traffic on the same connection. See the
7232 /// [`ordering`](crate::concepts::ordering) module for details.
7233 ///
7234 /// # When to Use
7235 ///
7236 /// Use this when:
7237 /// - You need to respond to a request based on another request's result
7238 /// - You want errors to automatically propagate to the request context
7239 /// - You only care about the success case
7240 ///
7241 /// For more control over error handling, use [`on_receiving_result`](Self::on_receiving_result).
7242 #[track_caller]
7243 pub fn on_receiving_ok_result<F>(
7244 self,
7245 responder: Responder<T>,
7246 task: impl FnOnce(T, Responder<T>) -> F + 'static + Send,
7247 ) -> Result<(), crate::Error>
7248 where
7249 F: Future<Output = Result<(), crate::Error>> + 'static + Send,
7250 T: JsonRpcResponse,
7251 {
7252 self.on_receiving_result(async move |result| match result {
7253 Ok(value) => task(value, responder).await,
7254 Err(err) => responder.respond_with_error(err),
7255 })
7256 }
7257
7258 /// Register an async callback to run when the response is received.
7259 ///
7260 /// This is the recommended way to select response handling from inside a handler because
7261 /// registration returns immediately. The response-consumption task waits concurrently for
7262 /// the response; once the response is dispatched, the ordered callback may hold the dispatch
7263 /// loop until it completes.
7264 ///
7265 /// # Example: Handle response in callback
7266 ///
7267 /// ```no_run
7268 /// # use agent_client_protocol_test::*;
7269 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
7270 /// # let connection = mock_connection();
7271 /// connection.on_receive_request(async |req: MyRequest, responder, cx| {
7272 /// // Send a request and schedule a callback for the response
7273 /// cx.send_request(QueryRequest { id: 22 })
7274 /// .on_receiving_result({
7275 /// let connection = cx.clone();
7276 /// async move |result| {
7277 /// match result {
7278 /// Ok(response) => {
7279 /// println!("Got response: {:?}", response);
7280 /// // Can send more messages here
7281 /// connection.send_notification(QueryComplete {})?;
7282 /// Ok(())
7283 /// }
7284 /// Err(error) => {
7285 /// eprintln!("Request failed: {}", error);
7286 /// Err(error)
7287 /// }
7288 /// }
7289 /// }
7290 /// })?;
7291 ///
7292 /// // Handler continues immediately after registering the callback
7293 /// responder.respond(MyResponse { status: "processing".into() })
7294 /// }, agent_client_protocol::on_receive_request!())
7295 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
7296 /// # Ok(())
7297 /// # }
7298 /// ```
7299 ///
7300 /// # Ordering
7301 ///
7302 /// When ordered consumption is selected before a peer response is routed during its original
7303 /// dispatch, the callback runs in a connection-managed task and the dispatch loop waits for
7304 /// it to complete before processing the next message.
7305 ///
7306 /// The barrier does not apply when the pending request is failed without an incoming response,
7307 /// such as on EOF. If the response was already routed, or an interceptor routes a retained
7308 /// [`ResponseRouter`] after its original dispatch, the callback still runs but cannot
7309 /// retroactively block messages that were already released.
7310 ///
7311 /// While the barrier is held, the callback must not await a later response, notification, or
7312 /// other inbound traffic on the same connection: that traffic cannot be dispatched until the
7313 /// callback completes. Spawn follow-up work with [`ConnectionTo::spawn`] and return, or use
7314 /// [`block_task`](Self::block_task) from a task already outside the dispatch loop.
7315 ///
7316 /// This differs from [`block_task`](Self::block_task), which does not select ordered
7317 /// consumption: dispatch remains free while the caller processes the delivered response.
7318 ///
7319 /// See the [`ordering`](crate::concepts::ordering) module for details on ordering guarantees
7320 /// and how to avoid deadlocks.
7321 ///
7322 /// # Error Handling
7323 ///
7324 /// If the scheduled task returns `Err`, the entire server will shut down. Make sure to handle
7325 /// errors appropriately within your task.
7326 ///
7327 /// # When to Use
7328 ///
7329 /// Use this method when:
7330 /// - You need to register response handling from a handler callback
7331 /// - You want a peer response callback to complete before later messages are dispatched
7332 /// - The callback performs bounded work that does not depend on later inbound traffic
7333 ///
7334 /// When already outside the dispatch loop and you do not need ordering guarantees, consider
7335 /// [`block_task`](Self::block_task).
7336 #[track_caller]
7337 pub fn on_receiving_result<F>(
7338 self,
7339 task: impl FnOnce(Result<T, crate::Error>) -> F + 'static + Send,
7340 ) -> Result<(), crate::Error>
7341 where
7342 T: 'static,
7343 F: Future<Output = Result<(), crate::Error>> + 'static + Send,
7344 {
7345 self.consume_with(move |response| match response {
7346 // Invoke the callback before constructing its future so the
7347 // response value does not need to be `Send` across an await.
7348 Ok(result) => Either::Left(task(result)),
7349 // A response that was never delivered fails the consuming
7350 // task instead of invoking the callback.
7351 Err(err) => Either::Right(future::ready(Err(err))),
7352 })
7353 }
7354}
7355
7356// ============================================================================
7357// IntoJrConnectionTransport Implementations
7358// ============================================================================
7359
7360/// A component that communicates over line streams.
7361///
7362/// `Lines` implements the [`ConnectTo`] trait for any pair of line-based streams
7363/// (a `Stream<Item = io::Result<String>>` for incoming and a `Sink<String>` for outgoing),
7364/// handling serialization of JSON-RPC messages to/from newline-delimited JSON.
7365/// An incoming line may contain one JSON-RPC message or a non-empty batch array. Batch
7366/// entries are dispatched individually in source order, and responses to the batch are
7367/// collected into one response-array line. SDK-initiated requests and notifications remain
7368/// individual messages.
7369///
7370/// This is a lower-level primitive than [`ByteStreams`] that enables interception and
7371/// transformation of individual lines before they are parsed or after they are serialized.
7372/// This is particularly useful for debugging, logging, or implementing custom line-based
7373/// protocols.
7374///
7375/// # Use Cases
7376///
7377/// - **Line-by-line logging**: Intercept and log each line before parsing
7378/// - **Custom protocols**: Transform lines before/after JSON-RPC processing
7379/// - **Debugging**: Inspect raw message strings
7380/// - **Line filtering**: Skip or modify specific messages
7381///
7382/// Most users should use [`ByteStreams`] instead, which provides a simpler interface
7383/// for byte-based I/O.
7384///
7385/// [`ConnectTo`]: crate::ConnectTo
7386#[derive(Debug)]
7387pub struct Lines<OutgoingSink, IncomingStream> {
7388 outgoing: OutgoingSink,
7389 incoming: IncomingStream,
7390}
7391
7392impl<OutgoingSink, IncomingStream> Lines<OutgoingSink, IncomingStream>
7393where
7394 OutgoingSink: futures::Sink<String, Error = std::io::Error> + Send + 'static,
7395 IncomingStream: futures::Stream<Item = std::io::Result<String>> + Send + 'static,
7396{
7397 /// Create a new line stream transport.
7398 pub fn new(outgoing: OutgoingSink, incoming: IncomingStream) -> Self {
7399 Self { outgoing, incoming }
7400 }
7401
7402 fn into_channel_transport(self) -> (Channel, crate::ConnectionDriver) {
7403 let Self { outgoing, incoming } = self;
7404 let (channel_for_caller, channel_for_lines) = Channel::duplex();
7405 let Channel { mut rx, tx } = channel_for_lines;
7406 let (finish_tx, finish_rx) = oneshot::channel();
7407 let finish = async move {
7408 // Losing a finish handle is not a shutdown request.
7409 if finish_rx.await.is_err() {
7410 future::pending::<()>().await;
7411 }
7412 }
7413 .boxed()
7414 .shared();
7415 let outgoing_frames = futures::stream::poll_fn({
7416 let mut finish = finish.clone();
7417 let mut finishing = false;
7418 move |cx| {
7419 if !finishing && std::pin::Pin::new(&mut finish).poll(cx).is_ready() {
7420 rx.close();
7421 finishing = true;
7422 }
7423 rx.poll_next_unpin(cx)
7424 }
7425 });
7426 let discard_incoming = Arc::new(AtomicBool::new(false));
7427 let incoming = incoming.filter_map({
7428 let discard_incoming = discard_incoming.clone();
7429 move |item| {
7430 let discard = discard_incoming.load(Ordering::Acquire);
7431 future::ready((!discard || item.is_err()).then_some(item))
7432 }
7433 });
7434 let outgoing = transport_actor::transport_outgoing_lines_actor(outgoing_frames, outgoing)
7435 .boxed()
7436 .shared();
7437 let serve_self = Box::pin({
7438 let outgoing = outgoing.clone();
7439 async move {
7440 futures::try_join!(
7441 outgoing,
7442 transport_actor::transport_incoming_lines_actor(incoming, tx),
7443 )?;
7444 Ok(())
7445 }
7446 });
7447 let server_future = crate::ConnectionDriver::with_finish(
7448 async move {
7449 match future::select(finish, serve_self).await {
7450 Either::Left(((), serve_self)) => {
7451 discard_incoming.store(true, Ordering::Release);
7452 // Keep reading while flushing, but do not require remote
7453 // read EOF. Poll incoming errors before clean sink drain.
7454 match future::select(serve_self, outgoing).await {
7455 Either::Left((result, _)) | Either::Right((result, _)) => result,
7456 }
7457 }
7458 Either::Right((result, _)) => result,
7459 }
7460 },
7461 move || {
7462 let _ = finish_tx.send(());
7463 },
7464 );
7465
7466 (channel_for_caller, server_future)
7467 }
7468}
7469
7470impl<OutgoingSink, IncomingStream, R: Role> ConnectTo<R> for Lines<OutgoingSink, IncomingStream>
7471where
7472 OutgoingSink: futures::Sink<String, Error = std::io::Error> + Send + 'static,
7473 IncomingStream: futures::Stream<Item = std::io::Result<String>> + Send + 'static,
7474{
7475 async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
7476 let (channel, mut serve_self) = self.into_channel_transport();
7477 let mut finish = serve_self
7478 .take_finish()
7479 .expect("built-in Lines transport supports explicit finishing");
7480 let client_future = Box::pin(ConnectTo::<R>::connect_to(channel, client));
7481
7482 match futures::future::select(client_future, serve_self).await {
7483 Either::Left((result, serve_self)) => {
7484 result?;
7485 // The local bridge has transferred all accepted client output.
7486 // Finish the physical sink without waiting for remote read EOF.
7487 finish.request();
7488 serve_self.await
7489 }
7490 Either::Right((result, _)) => result,
7491 }
7492 }
7493
7494 fn into_channel_and_future(self) -> (Channel, Option<crate::ConnectionDriver>) {
7495 let (channel, driver) = self.into_channel_transport();
7496 (channel, Some(driver))
7497 }
7498}
7499
7500/// A component that communicates over byte streams (stdin/stdout, sockets, pipes, etc.).
7501///
7502/// `ByteStreams` implements the [`ConnectTo`] trait for any pair of `AsyncRead` and `AsyncWrite`
7503/// streams, handling serialization of JSON-RPC messages to/from newline-delimited JSON.
7504/// This is the standard way to communicate with external processes or network connections.
7505///
7506/// # Use Cases
7507///
7508/// - **Stdio communication**: Connect to agents or proxies via stdin/stdout
7509/// - **Network sockets**: TCP, Unix domain sockets, or other stream-based protocols
7510/// - **Named pipes**: Cross-process communication on the same machine
7511/// - **File I/O**: Reading from and writing to file descriptors
7512///
7513/// # Example
7514///
7515/// Connecting to an agent via stdio:
7516///
7517/// ```no_run
7518/// use agent_client_protocol::UntypedRole;
7519/// # use agent_client_protocol::{ByteStreams};
7520/// use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
7521///
7522/// # async fn example() -> Result<(), agent_client_protocol::Error> {
7523/// let component = ByteStreams::new(
7524/// tokio::io::stdout().compat_write(),
7525/// tokio::io::stdin().compat(),
7526/// );
7527///
7528/// // Use as a component in a connection
7529/// agent_client_protocol::UntypedRole.builder()
7530/// .name("my-client")
7531/// .connect_to(component)
7532/// .await?;
7533/// # Ok(())
7534/// # }
7535/// ```
7536///
7537/// [`ConnectTo`]: crate::ConnectTo
7538#[derive(Debug)]
7539pub struct ByteStreams<OB, IB> {
7540 outgoing: OB,
7541 incoming: IB,
7542}
7543
7544impl<OB, IB> ByteStreams<OB, IB>
7545where
7546 OB: AsyncWrite + Send + 'static,
7547 IB: AsyncRead + Send + 'static,
7548{
7549 /// Create a new byte stream transport.
7550 pub fn new(outgoing: OB, incoming: IB) -> Self {
7551 Self { outgoing, incoming }
7552 }
7553
7554 fn into_lines(
7555 self,
7556 ) -> Lines<
7557 impl futures::Sink<String, Error = std::io::Error> + Send + 'static,
7558 impl futures::Stream<Item = std::io::Result<String>> + Send + 'static,
7559 > {
7560 use futures::AsyncBufReadExt;
7561 use futures::io::BufReader;
7562 let Self { outgoing, incoming } = self;
7563
7564 let incoming_lines = Box::pin(BufReader::new(incoming).lines());
7565 let outgoing_lines = transport_actor::LineWriter::new(outgoing);
7566
7567 Lines::new(outgoing_lines, incoming_lines)
7568 }
7569}
7570
7571#[cfg(any(
7572 all(
7573 any(feature = "process", feature = "stdio"),
7574 not(target_family = "wasm")
7575 ),
7576 test
7577))]
7578pub(crate) async fn write_line<W>(writer: &mut W, line: String) -> std::io::Result<()>
7579where
7580 W: AsyncWrite + Unpin + ?Sized,
7581{
7582 use futures::AsyncWriteExt as _;
7583
7584 let mut bytes = line.into_bytes();
7585 bytes.push(b'\n');
7586 writer.write_all(&bytes).await?;
7587 writer.flush().await
7588}
7589
7590impl<OB, IB, R: Role> ConnectTo<R> for ByteStreams<OB, IB>
7591where
7592 OB: AsyncWrite + Send + 'static,
7593 IB: AsyncRead + Send + 'static,
7594{
7595 async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
7596 ConnectTo::<R>::connect_to(self.into_lines(), client).await
7597 }
7598
7599 fn into_channel_and_future(self) -> (Channel, Option<crate::ConnectionDriver>) {
7600 ConnectTo::<R>::into_channel_and_future(self.into_lines())
7601 }
7602}
7603
7604/// A channel endpoint representing one side of a bidirectional JSON-RPC transport.
7605///
7606/// A channel carries complete TransportFrame values, preserving batch boundaries
7607/// across in-process components and transport adapters. Malformed wire input is an
7608/// explicit frame; failures while driving a physical transport are returned by that
7609/// transport's future.
7610///
7611/// # Example
7612///
7613/// ```no_run
7614/// # use agent_client_protocol::UntypedRole;
7615/// # use agent_client_protocol::Channel;
7616/// # async fn example() -> Result<(), agent_client_protocol::Error> {
7617/// let (channel_a, _channel_b) = Channel::duplex();
7618///
7619/// UntypedRole.builder()
7620/// .name("connection-a")
7621/// .connect_to(channel_a)
7622/// .await?;
7623/// # Ok(())
7624/// # }
7625/// ```
7626#[derive(Debug)]
7627pub struct Channel {
7628 /// Receives frames from the counterpart.
7629 pub rx: mpsc::UnboundedReceiver<TransportFrame>,
7630 /// Sends frames to the counterpart.
7631 pub tx: mpsc::UnboundedSender<TransportFrame>,
7632}
7633
7634impl Channel {
7635 /// Create a pair of connected channel endpoints.
7636 ///
7637 /// Frames sent through either endpoint are received by the other endpoint.
7638 #[must_use]
7639 pub fn duplex() -> (Self, Self) {
7640 let (a_tx, b_rx) = mpsc::unbounded();
7641 let (b_tx, a_rx) = mpsc::unbounded();
7642
7643 (Self { rx: a_rx, tx: a_tx }, Self { rx: b_rx, tx: b_tx })
7644 }
7645
7646 /// Copy frames from `rx` to `tx` until the input closes.
7647 ///
7648 /// # Errors
7649 ///
7650 /// Returns an error if the receiving endpoint closes before the input.
7651 pub(crate) async fn copy(mut self) -> Result<(), crate::Error> {
7652 while let Some(frame) = self.rx.next().await {
7653 self.tx
7654 .unbounded_send(frame)
7655 .map_err(crate::util::internal_error)?;
7656 }
7657 Ok(())
7658 }
7659
7660 /// Copy output concurrently with its owning driver, then drain accepted frames.
7661 /// Passive endpoints instead retain the channel's independent half-close lifetime.
7662 pub(crate) async fn copy_with_driver(
7663 self,
7664 driver: Option<crate::ConnectionDriver>,
7665 ) -> Result<(), crate::Error> {
7666 self.copy_with_driver_until(driver, future::pending()).await
7667 }
7668
7669 /// After the destination's owned foreground finishes, keep driving source
7670 /// errors and sink work, but never deliver queued or new input to it.
7671 pub(crate) async fn copy_with_driver_until(
7672 mut self,
7673 mut driver: Option<crate::ConnectionDriver>,
7674 stop_delivery: impl Future<Output = ()>,
7675 ) -> Result<(), crate::Error> {
7676 let mut stop_delivery = pin!(stop_delivery);
7677 let mut delivering = true;
7678 let mut done = false;
7679 loop {
7680 let event = future::poll_fn(|cx| {
7681 if delivering && stop_delivery.as_mut().poll(cx).is_ready() {
7682 delivering = false;
7683 }
7684 // Driver errors remain authoritative even when stop or EOF is ready.
7685 if !done
7686 && let Some(driver) = driver.as_mut()
7687 && let std::task::Poll::Ready(result) = std::pin::Pin::new(driver).poll(cx)
7688 {
7689 return std::task::Poll::Ready(Either::Left(result));
7690 }
7691 if !delivering && driver.is_none() {
7692 return std::task::Poll::Ready(Either::Right(None));
7693 }
7694 self.rx.poll_next_unpin(cx).map(Either::Right)
7695 })
7696 .await;
7697 let frame = match event {
7698 Either::Left(result) => {
7699 result?;
7700 done = true;
7701 self.rx.close();
7702 continue;
7703 }
7704 Either::Right(frame) => frame,
7705 };
7706 let Some(frame) = frame else {
7707 break;
7708 };
7709 if delivering {
7710 self.tx
7711 .unbounded_send(frame)
7712 .map_err(crate::util::internal_error)?;
7713 }
7714 }
7715 // Propagate this half-close before waiting for a still-running driver.
7716 drop(self);
7717 if !done && let Some(driver) = driver {
7718 driver.await?;
7719 }
7720 Ok(())
7721 }
7722
7723 /// Bridge two endpoints while inspecting every valid message.
7724 ///
7725 /// Observers are invoked in source order, including for each valid member of
7726 /// a batch. The original frame is forwarded unchanged after inspection.
7727 ///
7728 /// # Errors
7729 ///
7730 /// Returns an observer error or an error if a destination closes before its
7731 /// source.
7732 pub async fn bridge_with_inspection(
7733 left: Self,
7734 right: Self,
7735 mut left_to_right: impl FnMut(&RawJsonRpcMessage) -> Result<(), crate::Error> + Send,
7736 mut right_to_left: impl FnMut(&RawJsonRpcMessage) -> Result<(), crate::Error> + Send,
7737 ) -> Result<(), crate::Error> {
7738 let Self {
7739 rx: mut left_rx,
7740 tx: left_tx,
7741 } = left;
7742 let Self {
7743 rx: mut right_rx,
7744 tx: right_tx,
7745 } = right;
7746
7747 let left_to_right = async move {
7748 while let Some(frame) = left_rx.next().await {
7749 frame.inspect_messages(&mut left_to_right)?;
7750 right_tx
7751 .unbounded_send(frame)
7752 .map_err(crate::util::internal_error)?;
7753 }
7754 Ok::<(), crate::Error>(())
7755 };
7756 let right_to_left = async move {
7757 while let Some(frame) = right_rx.next().await {
7758 frame.inspect_messages(&mut right_to_left)?;
7759 left_tx
7760 .unbounded_send(frame)
7761 .map_err(crate::util::internal_error)?;
7762 }
7763 Ok::<(), crate::Error>(())
7764 };
7765
7766 futures::try_join!(left_to_right, right_to_left)?;
7767 Ok(())
7768 }
7769}
7770
7771impl<R: Role> ConnectTo<R> for Channel {
7772 async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
7773 let (client_channel, client_future) = client.into_channel_and_future();
7774
7775 let passive = client_future.is_none();
7776 let outgoing = Box::pin(
7777 Channel {
7778 rx: client_channel.rx,
7779 tx: self.tx,
7780 }
7781 .copy_with_driver(client_future),
7782 );
7783 let incoming = Box::pin(
7784 Channel {
7785 rx: self.rx,
7786 tx: client_channel.tx,
7787 }
7788 .copy(),
7789 );
7790 if passive {
7791 futures::try_join!(outgoing, incoming)?;
7792 return Ok(());
7793 }
7794
7795 match future::select(outgoing, incoming).await {
7796 Either::Left((result, _)) => result,
7797 Either::Right((result, outgoing)) => {
7798 result?;
7799 outgoing.await
7800 }
7801 }
7802 }
7803
7804 fn into_channel_and_future(self) -> (Channel, Option<crate::ConnectionDriver>) {
7805 (self, None)
7806 }
7807}
7808
7809#[cfg(test)]
7810mod tests {
7811 use super::*;
7812
7813 #[test]
7814 fn protected_cleanup_keeps_scoped_runners_polled_on_every_shutdown_path() {
7815 #[derive(Clone, Copy, Debug)]
7816 enum Stop {
7817 ForegroundSuccess,
7818 ForegroundError,
7819 InputEof,
7820 TransportError,
7821 TaskError,
7822 RunnerError,
7823 SupervisorError,
7824 }
7825
7826 struct Dropped(Arc<AtomicBool>);
7827 impl Drop for Dropped {
7828 fn drop(&mut self) {
7829 self.0.store(true, Ordering::Release);
7830 }
7831 }
7832
7833 for stop in [
7834 Stop::ForegroundSuccess,
7835 Stop::ForegroundError,
7836 Stop::InputEof,
7837 Stop::TransportError,
7838 Stop::TaskError,
7839 Stop::RunnerError,
7840 Stop::SupervisorError,
7841 ] {
7842 let cleaned = Arc::new(AtomicBool::new(false));
7843 let disposable_dropped = Arc::new(AtomicBool::new(false));
7844 let close_finished = Arc::new(AtomicBool::new(false));
7845 let (cleanup_tx, cleanup_rx) = oneshot::channel::<()>();
7846 let (scoped_done_tx, scoped_done_rx) = completion_signal();
7847 let (stop_tx, stop_rx) = oneshot::channel::<()>();
7848 let stop_signal = stop_rx.map(|_| ()).boxed().shared();
7849 let (incoming_tx, incoming_rx) = mpsc::unbounded();
7850 let outgoing = futures::sink::unfold((), |(), _line: String| {
7851 future::ready(Ok::<_, std::io::Error>(()))
7852 });
7853 let builder = Client
7854 .builder()
7855 .with_spawned({
7856 let cleaned = cleaned.clone();
7857 async move |cx: ConnectionTo<Agent>| {
7858 cx.shutdown_requested().await;
7859 // This stands in for the actual scoped native operation:
7860 // its async cleanup only advances if this runner is polled.
7861 cleanup_rx.await.unwrap();
7862 cleaned.store(true, Ordering::Release);
7863 let _ = scoped_done_tx.send(());
7864 Ok(())
7865 }
7866 })
7867 .with_spawned({
7868 let stop_signal = stop_signal.clone();
7869 async move |_cx| {
7870 stop_signal.await;
7871 if matches!(stop, Stop::RunnerError) {
7872 Err(crate::Error::internal_error().data("runner failure"))
7873 } else {
7874 future::pending().await
7875 }
7876 }
7877 })
7878 .on_close({
7879 let close_finished = close_finished.clone();
7880 let scoped_done = scoped_done_rx.clone();
7881 async move |cx: ConnectionTo<Agent>| {
7882 // EOF cancellation must precede, not await, close callbacks.
7883 cx.shutdown_requested().await;
7884 assert!(!cx.is_incoming_closed());
7885 scoped_done.await;
7886 close_finished.store(true, Ordering::Release);
7887 Ok(())
7888 }
7889 });
7890 let (connection, driver) =
7891 builder.into_connection_and_future(Lines::new(outgoing, incoming_rx), false, {
7892 let stop_signal = stop_signal.clone();
7893 async move |cx| {
7894 if matches!(stop, Stop::InputEof) {
7895 cx.incoming_closed().await;
7896 return Ok(());
7897 }
7898 stop_signal.await;
7899 match stop {
7900 Stop::ForegroundSuccess | Stop::SupervisorError => Ok(()),
7901 Stop::ForegroundError => {
7902 Err(crate::Error::internal_error().data("foreground failure"))
7903 }
7904 _ => future::pending().await,
7905 }
7906 }
7907 });
7908 let disposable = Dropped(disposable_dropped.clone());
7909 connection
7910 .spawn(async move {
7911 let _disposable = disposable;
7912 future::pending().await
7913 })
7914 .unwrap();
7915 connection
7916 .spawn({
7917 let stop_signal = stop_signal.clone();
7918 async move {
7919 stop_signal.await;
7920 if matches!(stop, Stop::TaskError) {
7921 Err(crate::Error::internal_error().data("task failure"))
7922 } else {
7923 future::pending().await
7924 }
7925 }
7926 })
7927 .unwrap();
7928 connection
7929 .spawn_protected({
7930 let connection = connection.clone();
7931 async move {
7932 connection.shutdown_requested().await;
7933 scoped_done_rx.await;
7934 if matches!(stop, Stop::SupervisorError) {
7935 Err(crate::Error::internal_error().data("supervisor failure"))
7936 } else {
7937 Ok(())
7938 }
7939 }
7940 })
7941 .unwrap();
7942 let mut driver = Box::pin(driver);
7943 assert!(driver.as_mut().now_or_never().is_none(), "{stop:?}");
7944 assert!(connection.shutdown_requested().now_or_never().is_none());
7945 let _ = stop_tx.send(());
7946 let incoming_tx = match stop {
7947 Stop::InputEof => {
7948 drop(incoming_tx);
7949 None
7950 }
7951 Stop::TransportError => {
7952 incoming_tx
7953 .unbounded_send(Err(std::io::Error::other("transport failure")))
7954 .unwrap();
7955 Some(incoming_tx)
7956 }
7957 _ => Some(incoming_tx),
7958 };
7959 for _ in 0..10 {
7960 assert!(driver.as_mut().now_or_never().is_none(), "{stop:?}");
7961 if connection.shutdown_requested().now_or_never().is_some() {
7962 break;
7963 }
7964 }
7965 assert!(
7966 connection.shutdown_requested().now_or_never().is_some(),
7967 "{stop:?}"
7968 );
7969 assert!(!cleaned.load(Ordering::Acquire), "{stop:?}");
7970 assert!(!disposable_dropped.load(Ordering::Acquire), "{stop:?}");
7971 cleanup_tx.send(()).unwrap();
7972 // Task acknowledgments may wake an actor already polled in this turn.
7973 // Bound the probe so a broken scoped-runner join fails, not hangs.
7974 let mut result = None;
7975 for _ in 0..10 {
7976 result = driver.as_mut().now_or_never();
7977 if result.is_some() {
7978 break;
7979 }
7980 }
7981 let result =
7982 result.unwrap_or_else(|| panic!("driver did not finish owned cleanup: {stop:?}"));
7983 match stop {
7984 Stop::ForegroundSuccess | Stop::InputEof => result.unwrap(),
7985 _ => {
7986 let error = result.expect_err("shutdown must preserve the first error");
7987 let expected = match stop {
7988 Stop::ForegroundError => "foreground failure",
7989 Stop::TransportError => "transport failure",
7990 Stop::TaskError => "task failure",
7991 Stop::RunnerError => "runner failure",
7992 Stop::SupervisorError => "supervisor failure",
7993 _ => unreachable!(),
7994 };
7995 assert!(
7996 error.data.unwrap().to_string().contains(expected),
7997 "{stop:?}"
7998 );
7999 }
8000 }
8001 assert!(cleaned.load(Ordering::Acquire), "{stop:?}");
8002 assert!(disposable_dropped.load(Ordering::Acquire), "{stop:?}");
8003 assert_eq!(
8004 close_finished.load(Ordering::Acquire),
8005 matches!(stop, Stop::InputEof),
8006 "{stop:?}",
8007 );
8008 assert!(connection.spawn_protected(async { Ok(()) }).is_err());
8009 drop(incoming_tx);
8010 }
8011 }
8012
8013 #[test]
8014 fn protected_operation_acknowledgments_are_reaped_and_join_seals_registration() {
8015 let (connection, _message_rx, _pending_replies) = connection_for_response_hook_tests();
8016 // The helper drops its task receiver, so use a live receiver for this probe.
8017 let (task_tx, mut task_rx) = mpsc::unbounded();
8018 let connection = ConnectionTo {
8019 task_tx,
8020 ..connection
8021 };
8022 for _ in 0..100 {
8023 connection.spawn_protected(async { Ok(()) }).unwrap();
8024 assert_eq!(
8025 connection
8026 .protected_operations
8027 .lock()
8028 .unwrap()
8029 .pending
8030 .len(),
8031 1
8032 );
8033 let task = task_rx.next().now_or_never().unwrap().unwrap();
8034 futures::executor::block_on(task.run_for_test()).unwrap();
8035 }
8036 assert!(
8037 connection
8038 .wait_protected_operations()
8039 .now_or_never()
8040 .is_some()
8041 );
8042 assert!(
8043 connection
8044 .wait_protected_operations()
8045 .now_or_never()
8046 .is_some()
8047 );
8048 assert!(
8049 connection
8050 .protected_operations
8051 .lock()
8052 .unwrap()
8053 .pending
8054 .is_empty()
8055 );
8056 assert!(connection.spawn_protected(async { Ok(()) }).is_err());
8057 assert!(task_rx.next().now_or_never().is_none());
8058 }
8059
8060 #[test]
8061 fn dropping_unused_finish_signal_preserves_physical_half_closes() {
8062 let outgoing = futures::sink::unfold((), |(), _line: String| {
8063 future::ready(Ok::<_, std::io::Error>(()))
8064 });
8065 let (incoming_tx, incoming_rx) = mpsc::unbounded();
8066 let (Channel { mut rx, tx }, mut driver) =
8067 Lines::new(outgoing, incoming_rx).into_channel_transport();
8068
8069 drop(
8070 driver
8071 .take_finish()
8072 .expect("built-in Lines driver is finishable"),
8073 );
8074 drop(tx);
8075 assert!((&mut driver).now_or_never().is_none());
8076 incoming_tx
8077 .unbounded_send(Ok(
8078 r#"{"jsonrpc":"2.0","method":"test/after-output-eof"}"#.into()
8079 ))
8080 .unwrap();
8081 assert!((&mut driver).now_or_never().is_none());
8082 assert!(rx.next().now_or_never().unwrap().is_some());
8083
8084 drop(incoming_tx);
8085 futures::executor::block_on(driver).unwrap();
8086 assert!(rx.next().now_or_never().unwrap().is_none());
8087 }
8088
8089 #[test]
8090 fn explicit_physical_finish_does_not_hide_a_ready_read_error() {
8091 let outgoing = futures::sink::unfold((), |(), _line: String| {
8092 future::ready(Ok::<_, std::io::Error>(()))
8093 });
8094 let incoming = futures::stream::iter([Err(std::io::Error::other("finish read failed"))]);
8095 let (_channel, mut driver) = Lines::new(outgoing, incoming).into_channel_transport();
8096 assert!(driver.request_finish());
8097
8098 let error = futures::executor::block_on(driver).unwrap_err();
8099 assert_eq!(
8100 error
8101 .data
8102 .and_then(|value| value.as_str().map(str::to_owned)),
8103 Some("finish read failed".into())
8104 );
8105 }
8106
8107 #[cfg(feature = "unstable_protocol_v2")]
8108 fn connection_with_task_receiver() -> (
8109 ConnectionTo<crate::role::UntypedRole>,
8110 mpsc::UnboundedReceiver<Task>,
8111 ) {
8112 let (message_tx, _message_rx) = mpsc::unbounded();
8113 let (task_tx, task_rx) = mpsc::unbounded();
8114 let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
8115 let transport_completion: SharedTransportCompletion =
8116 future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8117 let pending_replies = PendingReplies::default();
8118
8119 (
8120 ConnectionTo::new(
8121 crate::role::UntypedRole,
8122 message_tx,
8123 task_tx,
8124 dynamic_handler_tx,
8125 transport_completion,
8126 pending_replies.registrar(),
8127 ProtocolMode::disabled(),
8128 ),
8129 task_rx,
8130 )
8131 }
8132
8133 #[cfg(feature = "unstable_protocol_v2")]
8134 #[test]
8135 fn v2_builder_exposes_typed_context_to_user_callbacks() {
8136 fn assert_v2_context(_connection: &V2ConnectionTo<Agent>) {}
8137
8138 let _builder = Client
8139 .v2()
8140 .on_receive_request(
8141 async |_request: UntypedMessage, _responder, connection| {
8142 assert_v2_context(&connection);
8143 Ok(())
8144 },
8145 crate::on_receive_request!(),
8146 )
8147 .on_receive_notification(
8148 async |_notification: UntypedMessage, connection| {
8149 assert_v2_context(&connection);
8150 Ok(())
8151 },
8152 crate::on_receive_notification!(),
8153 )
8154 .on_receive_dispatch(
8155 async |_dispatch: Dispatch<UntypedMessage, UntypedMessage>, connection| {
8156 assert_v2_context(&connection);
8157 Ok(())
8158 },
8159 crate::on_receive_dispatch!(),
8160 )
8161 .on_receive_request_from(
8162 Agent,
8163 async |_request: UntypedMessage, _responder, connection| {
8164 assert_v2_context(&connection);
8165 Ok(())
8166 },
8167 crate::on_receive_request!(),
8168 )
8169 .on_receive_notification_from(
8170 Agent,
8171 async |_notification: UntypedMessage, connection| {
8172 assert_v2_context(&connection);
8173 Ok(())
8174 },
8175 crate::on_receive_notification!(),
8176 )
8177 .on_receive_dispatch_from(
8178 Agent,
8179 async |_dispatch: Dispatch<UntypedMessage, UntypedMessage>, connection| {
8180 assert_v2_context(&connection);
8181 Ok(())
8182 },
8183 crate::on_receive_dispatch!(),
8184 )
8185 .with_spawned(async |connection| {
8186 assert_v2_context(&connection);
8187 Ok(())
8188 })
8189 .on_close(async |connection| {
8190 assert_v2_context(&connection);
8191 Ok(())
8192 });
8193 }
8194
8195 #[cfg(feature = "unstable_protocol_v2")]
8196 #[test]
8197 fn proxy_builders_select_exact_proxy_protocol_guards() -> Result<(), crate::Error> {
8198 use crate::schema::ProtocolVersion;
8199
8200 for (mode, selected, unsupported) in [
8201 (
8202 Proxy.builder().protocol_mode,
8203 ProtocolVersion::V1,
8204 ProtocolVersion::V2,
8205 ),
8206 (
8207 Proxy.v2().protocol_mode,
8208 ProtocolVersion::V2,
8209 ProtocolVersion::V1,
8210 ),
8211 ] {
8212 assert_eq!(mode.api_protocol_version(), Some(selected));
8213
8214 let error = ProtocolCompat::new(mode)
8215 .incoming_message(UntypedMessage::new(
8216 "_proxy/initialize",
8217 serde_json::json!({ "protocolVersion": unsupported }),
8218 )?)
8219 .expect_err("a proxy builder must reject the other protocol version");
8220 let data = error
8221 .data
8222 .as_ref()
8223 .and_then(|data| data.as_str())
8224 .unwrap_or_default();
8225 assert!(
8226 data.contains(&format!("only supports ACP protocol version {selected}")),
8227 "{error:?}"
8228 );
8229 }
8230
8231 Ok(())
8232 }
8233
8234 #[cfg(feature = "unstable_protocol_v2")]
8235 #[test]
8236 fn v2_proxy_rejects_explicitly_prewrapped_initialize_request() {
8237 let (message_tx, message_rx) = mpsc::unbounded();
8238 let (task_tx, _task_rx) = mpsc::unbounded();
8239 let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
8240 let transport_completion: SharedTransportCompletion =
8241 future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8242 let pending_replies = PendingReplies::default();
8243 let connection = ConnectionTo::new(
8244 crate::Conductor,
8245 message_tx,
8246 task_tx,
8247 dynamic_handler_tx,
8248 transport_completion,
8249 pending_replies.registrar(),
8250 ProtocolMode::v2_proxy(),
8251 );
8252
8253 let request = crate::schema::SuccessorMessage {
8254 message: UntypedMessage::new(
8255 "initialize",
8256 serde_json::json!({ "protocolVersion": crate::schema::ProtocolVersion::V1 }),
8257 )
8258 .expect("test initialize request should serialize"),
8259 meta: None,
8260 };
8261 let sent = connection.send_request_to(Agent, request);
8262
8263 let (transport_tx, mut transport_rx) = mpsc::unbounded();
8264 let mut actor = Box::pin(outgoing_actor::outgoing_protocol_actor(
8265 message_rx,
8266 pending_replies,
8267 transport_tx,
8268 ProtocolCompat::new(ProtocolMode::v2_proxy()),
8269 future::pending::<()>().boxed().shared(),
8270 ));
8271 assert!(
8272 actor.as_mut().now_or_never().is_none(),
8273 "the outgoing actor should continue after rejecting the request"
8274 );
8275 assert!(
8276 transport_rx.next().now_or_never().is_none(),
8277 "an explicitly prewrapped initialize must not reach the transport"
8278 );
8279
8280 let error = futures::executor::block_on(sent.block_task())
8281 .expect_err("connection routing must own successor wrapping");
8282 let data = error
8283 .data
8284 .as_ref()
8285 .and_then(|data| data.as_str())
8286 .unwrap_or_default();
8287 assert!(data.contains("logical `initialize`"), "{error:?}");
8288 assert!(data.contains("_proxy/successor"), "{error:?}");
8289 }
8290
8291 #[cfg(feature = "unstable_protocol_v2")]
8292 #[test]
8293 fn v2_proxy_builder_exposes_typed_context_to_user_callbacks() {
8294 fn assert_v2_context(_connection: &V2ConnectionTo<crate::Conductor>) {}
8295
8296 let _builder = Proxy
8297 .v2()
8298 .on_receive_request_from(
8299 Client,
8300 async |_request: UntypedMessage, _responder, connection| {
8301 assert_v2_context(&connection);
8302 Ok(())
8303 },
8304 crate::on_receive_request!(),
8305 )
8306 .on_receive_notification_from(
8307 Agent,
8308 async |_notification: UntypedMessage, connection| {
8309 assert_v2_context(&connection);
8310 Ok(())
8311 },
8312 crate::on_receive_notification!(),
8313 )
8314 .on_receive_dispatch_from(
8315 Client,
8316 async |_dispatch: Dispatch<UntypedMessage, UntypedMessage>, connection| {
8317 assert_v2_context(&connection);
8318 Ok(())
8319 },
8320 crate::on_receive_dispatch!(),
8321 )
8322 .with_spawned(async |connection| {
8323 assert_v2_context(&connection);
8324 Ok(())
8325 })
8326 .on_close(async |connection| {
8327 assert_v2_context(&connection);
8328 Ok(())
8329 });
8330 }
8331
8332 #[cfg(feature = "unstable_protocol_v2")]
8333 #[test]
8334 fn raw_connection_spawns_v2_builder_with_typed_child_callback() {
8335 let (parent, mut task_rx) = connection_with_task_receiver();
8336 let (transport, _peer) = Channel::duplex();
8337 let (callback_tx, callback_rx) = oneshot::channel();
8338
8339 let child: ConnectionTo<Agent> = parent
8340 .spawn_connection::<Client>(
8341 Client
8342 .v2()
8343 .with_spawned(async move |_connection: V2ConnectionTo<Agent>| {
8344 callback_tx.send(()).map_err(|()| {
8345 crate::util::internal_error("typed child callback receiver was dropped")
8346 })
8347 }),
8348 transport,
8349 )
8350 .expect("v2 child connection should be spawned");
8351
8352 let task = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut task_rx))
8353 .expect("child connection task should already be queued")
8354 .expect("parent task queue should remain open");
8355 futures::executor::block_on(async {
8356 match future::select(Box::pin(task.run_for_test()), Box::pin(callback_rx)).await {
8357 Either::Right((Ok(()), child_task)) => drop(child_task),
8358 Either::Right((Err(error), _)) => {
8359 panic!("typed child callback sender was dropped: {error}")
8360 }
8361 Either::Left((result, _)) => {
8362 panic!("child connection stopped before its typed callback ran: {result:?}")
8363 }
8364 }
8365 });
8366
8367 drop(child);
8368 }
8369
8370 #[cfg(feature = "unstable_protocol_v2")]
8371 #[test]
8372 fn raw_connection_can_return_v2_context_for_spawned_builder() {
8373 let (parent, mut task_rx) = connection_with_task_receiver();
8374 let (transport, _peer) = Channel::duplex();
8375
8376 let child: V2ConnectionTo<Agent> = parent
8377 .spawn_connection_with_context(Client.v2(), transport)
8378 .expect("v2 child connection should be spawned");
8379
8380 let child_task = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut task_rx))
8381 .expect("child connection task should already be queued")
8382 .expect("parent task queue should remain open");
8383
8384 drop((child, child_task));
8385 }
8386
8387 fn connection_with_dynamic_handler_receiver() -> (
8388 ConnectionTo<crate::role::UntypedRole>,
8389 mpsc::UnboundedReceiver<DynamicHandlerMessage<crate::role::UntypedRole>>,
8390 ) {
8391 let (message_tx, _message_rx) = mpsc::unbounded();
8392 let (task_tx, _task_rx) = mpsc::unbounded();
8393 let (dynamic_handler_tx, dynamic_handler_rx) = mpsc::unbounded();
8394 let transport_completion: SharedTransportCompletion =
8395 future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8396 let pending_replies = PendingReplies::default();
8397
8398 (
8399 ConnectionTo::new(
8400 crate::role::UntypedRole,
8401 message_tx,
8402 task_tx,
8403 dynamic_handler_tx,
8404 transport_completion,
8405 pending_replies.registrar(),
8406 ProtocolMode::disabled(),
8407 ),
8408 dynamic_handler_rx,
8409 )
8410 }
8411
8412 struct ClaimingDynamicHandler;
8413
8414 impl HandleDispatchFrom<crate::role::UntypedRole> for ClaimingDynamicHandler {
8415 fn handle_dispatch_from(
8416 &mut self,
8417 _message: Dispatch,
8418 _connection: ConnectionTo<crate::role::UntypedRole>,
8419 ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
8420 future::ready(Ok(Handled::Yes))
8421 }
8422
8423 fn describe_chain(&self) -> impl Debug {
8424 "ClaimingDynamicHandler"
8425 }
8426 }
8427
8428 fn connection_for_response_hook_tests() -> (
8429 ConnectionTo<crate::role::UntypedRole>,
8430 mpsc::UnboundedReceiver<OutgoingMessage>,
8431 PendingReplies,
8432 ) {
8433 let (message_tx, message_rx) = mpsc::unbounded();
8434 let (task_tx, _task_rx) = mpsc::unbounded();
8435 let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
8436 let transport_completion: SharedTransportCompletion =
8437 future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8438 let pending_replies = PendingReplies::default();
8439
8440 (
8441 ConnectionTo::new(
8442 crate::role::UntypedRole,
8443 message_tx,
8444 task_tx,
8445 dynamic_handler_tx,
8446 transport_completion,
8447 pending_replies.registrar(),
8448 ProtocolMode::disabled(),
8449 ),
8450 message_rx,
8451 pending_replies,
8452 )
8453 }
8454
8455 #[cfg(feature = "unstable_protocol_v2")]
8456 fn route_test_response(
8457 request_id: RequestId,
8458 pending_replies: &PendingReplies,
8459 result: Result<serde_json::Value, crate::Error>,
8460 ) {
8461 let pending_reply = pending_replies
8462 .remove(&request_id)
8463 .expect("the request should have a pending reply");
8464 let (dispatch, _) =
8465 incoming_actor::dispatch_from_response(request_id, pending_reply, result);
8466 let Dispatch::Response(result, router) = dispatch else {
8467 panic!("expected a response dispatch");
8468 };
8469 router
8470 .route_with_result(result)
8471 .expect("response should route to the pending request");
8472 }
8473
8474 #[cfg(feature = "unstable_protocol_v2")]
8475 #[test]
8476 fn response_hook_runs_when_success_is_routed_before_consumption() {
8477 let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
8478 let hook_ran = Arc::new(AtomicBool::new(false));
8479 let sent = connection.send_request_to_with_response_hook_after(
8480 crate::role::UntypedRole,
8481 UntypedMessage::new("hooked", serde_json::json!({}))
8482 .expect("test request should serialize"),
8483 future::ready(Ok(())),
8484 {
8485 let hook_ran = hook_ran.clone();
8486 move |response| {
8487 assert_eq!(response, &serde_json::json!({"ok": true}));
8488 hook_ran.store(true, Ordering::Release);
8489 Ok(())
8490 }
8491 },
8492 );
8493 let request_id = sent.id().clone();
8494
8495 route_test_response(
8496 request_id,
8497 &pending_replies,
8498 Ok(serde_json::json!({"ok": true})),
8499 );
8500
8501 assert!(hook_ran.load(Ordering::Acquire));
8502 assert_eq!(
8503 futures::executor::block_on(sent.block_task())
8504 .expect("routed response should remain consumable"),
8505 serde_json::json!({"ok": true})
8506 );
8507 }
8508
8509 #[cfg(feature = "unstable_protocol_v2")]
8510 #[test]
8511 fn response_hook_skips_errors_but_outlives_a_dropped_consumer() {
8512 let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
8513 let peer_error_hook_ran = Arc::new(AtomicBool::new(false));
8514 let peer_error = connection.send_request_to_with_response_hook_after(
8515 crate::role::UntypedRole,
8516 UntypedMessage::new("peer-error", serde_json::json!({}))
8517 .expect("test request should serialize"),
8518 future::ready(Ok(())),
8519 {
8520 let hook_ran = peer_error_hook_ran.clone();
8521 move |_| {
8522 hook_ran.store(true, Ordering::Release);
8523 Ok(())
8524 }
8525 },
8526 );
8527 let peer_error_id = peer_error.id().clone();
8528 route_test_response(
8529 peer_error_id,
8530 &pending_replies,
8531 Err(crate::Error::invalid_request()),
8532 );
8533 assert!(
8534 futures::executor::block_on(peer_error.block_task()).is_err(),
8535 "the peer error should reach the consumer"
8536 );
8537 assert!(!peer_error_hook_ran.load(Ordering::Acquire));
8538
8539 let dropped_hook_ran = Arc::new(AtomicBool::new(false));
8540 let dropped = connection.send_request_to_with_response_hook_after(
8541 crate::role::UntypedRole,
8542 UntypedMessage::new("dropped", serde_json::json!({}))
8543 .expect("test request should serialize"),
8544 future::ready(Ok(())),
8545 {
8546 let hook_ran = dropped_hook_ran.clone();
8547 move |_| {
8548 hook_ran.store(true, Ordering::Release);
8549 Ok(())
8550 }
8551 },
8552 );
8553 let dropped_id = dropped.id().clone();
8554 drop(dropped);
8555 route_test_response(
8556 dropped_id,
8557 &pending_replies,
8558 Ok(serde_json::json!({"ok": true})),
8559 );
8560 assert!(dropped_hook_ran.load(Ordering::Acquire));
8561 }
8562
8563 #[cfg(feature = "unstable_protocol_v2")]
8564 #[test]
8565 fn response_hook_failure_replaces_the_success_result() {
8566 let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
8567 let sent = connection.send_request_to_with_response_hook_after(
8568 crate::role::UntypedRole,
8569 UntypedMessage::new("hook-failure", serde_json::json!({}))
8570 .expect("test request should serialize"),
8571 future::ready(Ok(())),
8572 |_| Err(crate::Error::internal_error().data("response hook failed")),
8573 );
8574 let request_id = sent.id().clone();
8575 route_test_response(
8576 request_id,
8577 &pending_replies,
8578 Ok(serde_json::json!({"ok": true})),
8579 );
8580
8581 let error = futures::executor::block_on(sent.block_task())
8582 .expect_err("the hook failure should replace the successful response");
8583 assert_eq!(error.code, crate::ErrorCode::InternalError);
8584 assert_eq!(error.data, Some(serde_json::json!("response hook failed")));
8585 }
8586
8587 #[test]
8588 fn ordered_request_waits_for_readiness_before_publication() {
8589 let (connection, message_rx, pending_replies) = connection_for_response_hook_tests();
8590 let (ready_tx, ready_rx) = oneshot::channel();
8591 let sent = connection.send_ordered_request_to_after(
8592 crate::role::UntypedRole,
8593 UntypedMessage::new("after-ready", serde_json::json!({}))
8594 .expect("test request should serialize"),
8595 async move { ready_rx.await.map_err(crate::Error::into_internal_error) },
8596 );
8597
8598 let (transport_tx, mut transport_rx) = mpsc::unbounded();
8599 let mut actor = Box::pin(outgoing_actor::outgoing_protocol_actor(
8600 message_rx,
8601 pending_replies,
8602 transport_tx,
8603 ProtocolCompat::new(ProtocolMode::disabled()),
8604 future::pending::<()>().boxed().shared(),
8605 ));
8606
8607 assert!(
8608 actor.as_mut().now_or_never().is_none(),
8609 "the outgoing actor should wait for readiness"
8610 );
8611 assert!(
8612 transport_rx.next().now_or_never().is_none(),
8613 "the request must not be published before readiness"
8614 );
8615
8616 ready_tx
8617 .send(())
8618 .expect("the readiness receiver should remain active");
8619 assert!(
8620 actor.as_mut().now_or_never().is_none(),
8621 "the outgoing actor should continue serving after publication"
8622 );
8623 let frame = transport_rx
8624 .next()
8625 .now_or_never()
8626 .expect("the ready request should be published")
8627 .expect("the transport queue should remain open");
8628 assert!(matches!(
8629 frame,
8630 TransportFrame::Single(RawJsonRpcMessage::Request(_))
8631 ));
8632
8633 drop(sent);
8634 }
8635
8636 #[test]
8637 fn foreground_finish_settles_unready_requests_and_preserves_ready_output_fifo() {
8638 let (connection, message_rx, pending_replies) = connection_for_response_hook_tests();
8639 let unready = connection.send_ordered_request_to_after(
8640 crate::role::UntypedRole,
8641 UntypedMessage::new("unready", serde_json::json!({})).unwrap(),
8642 future::pending(),
8643 );
8644 let unready_id = unready.id().clone();
8645 send_raw_message(
8646 &connection.message_tx,
8647 OutgoingMessage::Notification {
8648 untyped: UntypedMessage::new("first", serde_json::json!({})).unwrap(),
8649 },
8650 )
8651 .unwrap();
8652 let ready = connection.send_ordered_request_to_after(
8653 crate::role::UntypedRole,
8654 UntypedMessage::new("ready", serde_json::json!({})).unwrap(),
8655 future::ready(Ok(())),
8656 );
8657 let unready_after = connection.send_ordered_request_to_after(
8658 crate::role::UntypedRole,
8659 UntypedMessage::new("unready-after", serde_json::json!({})).unwrap(),
8660 future::pending(),
8661 );
8662 let unready_after_id = unready_after.id().clone();
8663 send_raw_message(
8664 &connection.message_tx,
8665 OutgoingMessage::Notification {
8666 untyped: UntypedMessage::new("last", serde_json::json!({})).unwrap(),
8667 },
8668 )
8669 .unwrap();
8670 let (done_tx, done_rx) = oneshot::channel();
8671 send_raw_message(
8672 &connection.message_tx,
8673 OutgoingMessage::CloseAfterDraining { done: done_tx },
8674 )
8675 .unwrap();
8676 let (transport_tx, transport_rx) = mpsc::unbounded();
8677 futures::executor::block_on(outgoing_actor::outgoing_protocol_actor(
8678 message_rx,
8679 pending_replies.clone(),
8680 transport_tx,
8681 ProtocolCompat::new(ProtocolMode::disabled()),
8682 future::ready(()).boxed().shared(),
8683 ))
8684 .unwrap();
8685 futures::executor::block_on(done_rx).unwrap();
8686 let error = futures::executor::block_on(unready.block_task())
8687 .expect_err("an unresolved gate must explicitly fail its consumer");
8688 assert!(
8689 error
8690 .data
8691 .unwrap()
8692 .to_string()
8693 .contains("foreground completed before outgoing request readiness")
8694 );
8695 assert!(!pending_replies.contains(&unready_id));
8696 let error = futures::executor::block_on(unready_after.block_task())
8697 .expect_err("each unresolved gate must fail without repolling a consumed signal");
8698 assert!(
8699 error
8700 .data
8701 .unwrap()
8702 .to_string()
8703 .contains("foreground completed before outgoing request readiness")
8704 );
8705 assert!(!pending_replies.contains(&unready_after_id));
8706 assert!(pending_replies.contains(ready.id()));
8707 let frames = futures::executor::block_on(transport_rx.collect::<Vec<_>>());
8708 let methods = frames
8709 .into_iter()
8710 .map(|frame| match frame {
8711 TransportFrame::Single(RawJsonRpcMessage::Notification(message)) => {
8712 message.method.to_string()
8713 }
8714 TransportFrame::Single(RawJsonRpcMessage::Request(message)) => {
8715 message.method.to_string()
8716 }
8717 _ => panic!("expected ready request/notification output"),
8718 })
8719 .collect::<Vec<_>>();
8720 assert_eq!(methods, ["first", "ready", "last"]);
8721 }
8722
8723 #[test]
8724 fn ordered_blocking_transform_precedes_response_acknowledgment() {
8725 let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
8726 let sent = connection.send_ordered_request_to(
8727 crate::role::UntypedRole,
8728 UntypedMessage::new("ordered-transform", serde_json::json!({}))
8729 .expect("test request should serialize"),
8730 );
8731 let request_id = sent.id().clone();
8732 let pending_reply = pending_replies
8733 .remove(&request_id)
8734 .expect("the request should have a pending reply");
8735 let (dispatch, response_dispatch) = incoming_actor::dispatch_from_response(
8736 request_id,
8737 pending_reply,
8738 Err(crate::Error::invalid_params()),
8739 );
8740 let Dispatch::Response(result, router) = dispatch else {
8741 panic!("expected a response dispatch");
8742 };
8743 router
8744 .route_with_result(result)
8745 .expect("response should route to the pending request");
8746 let acknowledgment = response_dispatch
8747 .complete()
8748 .expect("an ordered response should wait for acknowledgment");
8749 let acknowledgment = Arc::new(Mutex::new(Some(acknowledgment)));
8750 let acknowledgment_probe = acknowledgment.clone();
8751
8752 let error =
8753 futures::executor::block_on(sent.block_task_with_ordered_result(move |result| {
8754 assert_eq!(
8755 acknowledgment_probe
8756 .lock()
8757 .expect("acknowledgment mutex poisoned")
8758 .as_mut()
8759 .expect("acknowledgment receiver should remain available")
8760 .try_recv()
8761 .expect("acknowledgment sender should remain open"),
8762 None,
8763 "the ordered response was acknowledged before its transform"
8764 );
8765 result
8766 }))
8767 .expect_err("the peer error should survive the ordered transform");
8768 assert_eq!(error.code, crate::ErrorCode::InvalidParams);
8769
8770 let acknowledgment = acknowledgment
8771 .lock()
8772 .expect("acknowledgment mutex poisoned")
8773 .take()
8774 .expect("acknowledgment receiver should remain available");
8775 futures::executor::block_on(acknowledgment)
8776 .expect("the transform should release the ordered response");
8777 }
8778
8779 #[cfg(feature = "unstable_protocol_v2")]
8780 #[test]
8781 fn outgoing_request_readiness_failure_rejects_without_publication() {
8782 let (connection, message_rx, pending_replies) = connection_for_response_hook_tests();
8783 let hook_ran = Arc::new(AtomicBool::new(false));
8784 let sent = connection.send_request_to_with_response_hook_after(
8785 crate::role::UntypedRole,
8786 UntypedMessage::new("never-published", serde_json::json!({}))
8787 .expect("test request should serialize"),
8788 future::ready(Err(crate::Error::internal_error().data("readiness failed"))),
8789 {
8790 let hook_ran = hook_ran.clone();
8791 move |_| {
8792 hook_ran.store(true, Ordering::Release);
8793 Ok(())
8794 }
8795 },
8796 );
8797
8798 let (transport_tx, mut transport_rx) = mpsc::unbounded();
8799 let mut actor = Box::pin(outgoing_actor::outgoing_protocol_actor(
8800 message_rx,
8801 pending_replies,
8802 transport_tx,
8803 ProtocolCompat::new(ProtocolMode::disabled()),
8804 future::pending::<()>().boxed().shared(),
8805 ));
8806
8807 assert!(
8808 actor.as_mut().now_or_never().is_none(),
8809 "the outgoing actor should continue serving after rejecting the request"
8810 );
8811 assert!(
8812 transport_rx.next().now_or_never().is_none(),
8813 "a request whose readiness failed must not be published"
8814 );
8815 let error = futures::executor::block_on(sent.block_task())
8816 .expect_err("the readiness error should reach the request consumer");
8817 assert_eq!(error.code, crate::ErrorCode::InternalError);
8818 assert_eq!(error.data, Some(serde_json::json!("readiness failed")));
8819 assert!(!hook_ran.load(Ordering::Acquire));
8820 }
8821
8822 #[test]
8823 fn ordered_request_is_marked_before_entering_outgoing_queue() {
8824 let (message_tx, mut message_rx) = mpsc::unbounded();
8825 let (task_tx, mut task_rx) = mpsc::unbounded();
8826 let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
8827 let transport_completion: SharedTransportCompletion =
8828 future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8829 let pending_replies = PendingReplies::default();
8830 let connection = ConnectionTo::new(
8831 crate::role::UntypedRole,
8832 message_tx,
8833 task_tx,
8834 dynamic_handler_tx,
8835 transport_completion,
8836 pending_replies.registrar(),
8837 ProtocolMode::disabled(),
8838 );
8839
8840 let sent = connection.send_ordered_request_to(
8841 crate::role::UntypedRole,
8842 UntypedMessage::new("ordered", serde_json::json!({}))
8843 .expect("test request should serialize"),
8844 );
8845 let request_id = sent.id().clone();
8846 let message = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut message_rx))
8847 .expect("outgoing request should already be queued")
8848 .expect("outgoing request queue should remain open");
8849 let OutgoingMessage::Request { id, .. } = message else {
8850 panic!("expected an outgoing request");
8851 };
8852 assert_eq!(id, request_id);
8853
8854 let pending_reply = pending_replies
8855 .remove(&request_id)
8856 .expect("the request should have a pending reply");
8857 assert!(
8858 pending_reply.ordering.is_ordered(),
8859 "the response ordering barrier must be installed before publication"
8860 );
8861
8862 // Route the response before the callback is registered. The pre-set
8863 // ordering marker must hold dispatch until the callback task is
8864 // subsequently installed and completes.
8865 let (dispatch, response_dispatch) = incoming_actor::dispatch_from_response(
8866 request_id,
8867 pending_reply,
8868 Ok(serde_json::json!({"ok": true})),
8869 );
8870 let Dispatch::Response(result, router) = dispatch else {
8871 panic!("expected a response dispatch");
8872 };
8873 router
8874 .route_with_result(result)
8875 .expect("response should route to the pending request");
8876 let acknowledgment = response_dispatch
8877 .complete()
8878 .expect("an ordered response should require acknowledgment");
8879
8880 let callback_ran = Arc::new(AtomicBool::new(false));
8881 sent.on_receiving_result({
8882 let callback_ran = callback_ran.clone();
8883 async move |result| {
8884 assert_eq!(result?, serde_json::json!({"ok": true}));
8885 callback_ran.store(true, Ordering::Release);
8886 Ok(())
8887 }
8888 })
8889 .expect("ordered callback should be scheduled");
8890
8891 let task = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut task_rx))
8892 .expect("callback task should already be queued")
8893 .expect("callback task queue should remain open");
8894 futures::executor::block_on(task.run_for_test()).expect("callback task should succeed");
8895 futures::executor::block_on(acknowledgment)
8896 .expect("callback completion should acknowledge dispatch");
8897 assert!(callback_ran.load(Ordering::Acquire));
8898 }
8899
8900 fn next_dynamic_handler_message<Counterpart: Role>(
8901 receiver: &mut mpsc::UnboundedReceiver<DynamicHandlerMessage<Counterpart>>,
8902 ) -> Option<DynamicHandlerMessage<Counterpart>> {
8903 futures::FutureExt::now_or_never(futures::StreamExt::next(receiver))
8904 .expect("dynamic-handler receiver should be ready")
8905 }
8906
8907 #[cfg(feature = "unstable_protocol_v2")]
8908 #[test]
8909 fn v2_dynamic_handler_guard_registers_and_removes_handler() {
8910 let (message_tx, _message_rx) = mpsc::unbounded();
8911 let (task_tx, _task_rx) = mpsc::unbounded();
8912 let (dynamic_handler_tx, mut dynamic_handler_rx) = mpsc::unbounded();
8913 let transport_completion: SharedTransportCompletion =
8914 future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8915 let pending_replies = PendingReplies::default();
8916 let connection = V2ConnectionTo {
8917 inner: ConnectionTo::new(
8918 Agent,
8919 message_tx,
8920 task_tx,
8921 dynamic_handler_tx,
8922 transport_completion,
8923 pending_replies.registrar(),
8924 ProtocolMode::v2_client(),
8925 ),
8926 };
8927
8928 let guard = connection
8929 .add_dynamic_handler(NullHandler)
8930 .expect("v2 dynamic handler should register");
8931 let added_uuid = match next_dynamic_handler_message(&mut dynamic_handler_rx) {
8932 Some(DynamicHandlerMessage::AddDynamicHandler(uuid, _)) => uuid,
8933 other => panic!("expected v2 handler registration, got {other:?}"),
8934 };
8935
8936 drop(guard);
8937
8938 match next_dynamic_handler_message(&mut dynamic_handler_rx) {
8939 Some(DynamicHandlerMessage::RemoveDynamicHandler(uuid)) => {
8940 assert_eq!(uuid, added_uuid);
8941 }
8942 other => panic!("expected v2 handler removal, got {other:?}"),
8943 }
8944 }
8945
8946 #[test]
8947 fn dropping_dynamic_handler_guard_unregisters_handler() {
8948 let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
8949 let guard = connection.add_dynamic_handler(NullHandler).unwrap();
8950
8951 let added_uuid = match next_dynamic_handler_message(&mut receiver) {
8952 Some(DynamicHandlerMessage::AddDynamicHandler(uuid, _)) => uuid,
8953 other => panic!("expected handler registration, got {other:?}"),
8954 };
8955
8956 drop(guard);
8957
8958 match next_dynamic_handler_message(&mut receiver) {
8959 Some(DynamicHandlerMessage::RemoveDynamicHandler(uuid)) => {
8960 assert_eq!(uuid, added_uuid);
8961 }
8962 other => panic!("expected handler removal, got {other:?}"),
8963 }
8964 }
8965
8966 #[test]
8967 fn dropping_dynamic_handler_guard_deactivates_queued_handler_immediately() {
8968 let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
8969 let guard = connection
8970 .add_dynamic_handler(ClaimingDynamicHandler)
8971 .expect("dynamic handler should register");
8972 let mut handler = match next_dynamic_handler_message(&mut receiver) {
8973 Some(DynamicHandlerMessage::AddDynamicHandler(_, handler)) => handler,
8974 other => panic!("expected handler registration, got {other:?}"),
8975 };
8976
8977 drop(guard);
8978
8979 let message = Dispatch::Notification(
8980 UntypedMessage::new("stale", serde_json::json!({}))
8981 .expect("test notification should serialize"),
8982 );
8983 let handled =
8984 futures::executor::block_on(handler.dyn_handle_dispatch_from(message, connection))
8985 .expect("inactive handler should decline cleanly");
8986 assert!(matches!(handled, Handled::No { retry: false, .. }));
8987 }
8988
8989 #[test]
8990 fn dynamic_handler_barrier_acknowledges_prior_messages() {
8991 let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
8992 let _guard = connection.add_dynamic_handler(NullHandler).unwrap();
8993 let mut barrier = Box::pin(connection.dynamic_handler_barrier());
8994
8995 assert!(matches!(
8996 next_dynamic_handler_message(&mut receiver),
8997 Some(DynamicHandlerMessage::AddDynamicHandler(_, _))
8998 ));
8999 assert!(
9000 barrier.as_mut().now_or_never().is_none(),
9001 "the barrier must wait for the incoming actor"
9002 );
9003
9004 let acknowledgment = match next_dynamic_handler_message(&mut receiver) {
9005 Some(DynamicHandlerMessage::AcknowledgedBarrier(acknowledgment)) => acknowledgment,
9006 other => panic!("expected acknowledged barrier, got {other:?}"),
9007 };
9008 acknowledgment
9009 .send(())
9010 .expect("the barrier receiver should remain active");
9011 futures::executor::block_on(barrier)
9012 .expect("the acknowledged dynamic-handler barrier should complete");
9013 }
9014
9015 #[test]
9016 fn detaching_dynamic_handler_guard_does_not_leak_connection() {
9017 let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
9018 let guard = connection.add_dynamic_handler(NullHandler).unwrap();
9019
9020 assert!(matches!(
9021 next_dynamic_handler_message(&mut receiver),
9022 Some(DynamicHandlerMessage::AddDynamicHandler(_, _))
9023 ));
9024
9025 drop(connection);
9026 guard.detach();
9027
9028 assert!(
9029 next_dynamic_handler_message(&mut receiver).is_none(),
9030 "detach should retain the handler without retaining a connection sender"
9031 );
9032 }
9033
9034 #[tokio::test]
9035 async fn write_line_flushes_buffered_writers() {
9036 let mut writer =
9037 futures::io::BufWriter::with_capacity(4096, futures::io::Cursor::new(Vec::new()));
9038
9039 write_line(&mut writer, "message".into()).await.unwrap();
9040
9041 assert_eq!(writer.into_inner().into_inner(), b"message\n");
9042 }
9043
9044 #[test]
9045 fn peel_successor_envelopes_returns_plain_messages_unchanged() {
9046 let params = serde_json::json!({ "key": "value" });
9047 let (method, peeled) = peel_successor_envelopes("session/update", ¶ms);
9048 assert_eq!(method, "session/update");
9049 assert_eq!(peeled, ¶ms);
9050 }
9051
9052 #[test]
9053 fn peel_successor_envelopes_unwraps_nested_envelopes() {
9054 let params = serde_json::json!({
9055 "method": "_proxy/successor",
9056 "params": {
9057 "method": "$/cancel_request",
9058 "params": { "requestId": "req-1" }
9059 }
9060 });
9061 let (method, peeled) = peel_successor_envelopes("_proxy/successor", ¶ms);
9062 assert_eq!(method, "$/cancel_request");
9063 assert_eq!(peeled, &serde_json::json!({ "requestId": "req-1" }));
9064 }
9065
9066 #[test]
9067 fn peel_successor_envelopes_leaves_malformed_envelopes_intact() {
9068 // No string `method` field: the envelope cannot be peeled, so the
9069 // message is returned as-is for the handler chain to deal with.
9070 let params = serde_json::json!({ "unexpected": true });
9071 let (method, peeled) = peel_successor_envelopes("_proxy/successor", ¶ms);
9072 assert_eq!(method, "_proxy/successor");
9073 assert_eq!(peeled, ¶ms);
9074 }
9075
9076 mod cancel_request {
9077 use super::super::*;
9078
9079 fn notification(method: &str, params: serde_json::Value) -> UntypedMessage {
9080 UntypedMessage::new(method, params).expect("well-formed JSON")
9081 }
9082
9083 #[test]
9084 fn cancellation_request_id_is_extracted_from_wrapped_notifications() {
9085 let message = notification(
9086 "_proxy/successor",
9087 serde_json::json!({
9088 "method": "$/cancel_request",
9089 "params": { "requestId": "req-1" }
9090 }),
9091 );
9092 let request_id = cancellation_request_id_from_message(&message)
9093 .expect("wrapped cancel should parse");
9094 assert_eq!(request_id, Some(RequestId::Str("req-1".into())));
9095 }
9096
9097 #[test]
9098 fn malformed_successor_envelope_is_not_treated_as_cancellation() {
9099 // The envelope cannot be peeled; the message must flow on to the
9100 // handler chain instead of erroring the dispatch.
9101 let message = notification("_proxy/successor", serde_json::json!({ "bogus": true }));
9102 let request_id = cancellation_request_id_from_message(&message)
9103 .expect("malformed envelope should be left to the handler chain");
9104 assert_eq!(request_id, None);
9105 }
9106
9107 #[test]
9108 fn cancel_request_notifications_are_detected_even_when_wrapped() {
9109 let plain = notification("$/cancel_request", serde_json::json!({ "requestId": 1 }));
9110 assert!(is_cancel_request_notification(&plain));
9111
9112 let wrapped = notification(
9113 "_proxy/successor",
9114 serde_json::json!({
9115 "method": "$/cancel_request",
9116 "params": { "requestId": 1 }
9117 }),
9118 );
9119 assert!(is_cancel_request_notification(&wrapped));
9120
9121 let other_wrapped = notification(
9122 "_proxy/successor",
9123 serde_json::json!({
9124 "method": "session/update",
9125 "params": {}
9126 }),
9127 );
9128 assert!(!is_cancel_request_notification(&other_wrapped));
9129
9130 let malformed_envelope =
9131 notification("_proxy/successor", serde_json::json!({ "bogus": true }));
9132 assert!(!is_cancel_request_notification(&malformed_envelope));
9133 }
9134
9135 #[test]
9136 fn malformed_cancel_request_params_error() {
9137 let message = notification(
9138 "$/cancel_request",
9139 serde_json::json!({ "requestId": { "not": "an id" } }),
9140 );
9141 cancellation_request_id_from_message(&message)
9142 .expect_err("malformed cancel params should error");
9143 }
9144
9145 #[test]
9146 fn registry_marks_and_removes_requests() {
9147 let registry = RequestCancellationRegistry::new();
9148 let id = RequestId::Str("req-1".into());
9149
9150 let responder_cancellation = registry.register(&id);
9151 let marker = responder_cancellation.cancellation();
9152 assert!(!marker.is_cancelled());
9153
9154 assert!(registry.cancel(&id));
9155 assert!(marker.is_cancelled());
9156 assert!(responder_cancellation.cancellation().is_cancelled());
9157
9158 drop(responder_cancellation);
9159 assert!(!registry.cancel(&id), "slot should be removed on drop");
9160 }
9161
9162 #[test]
9163 fn reused_request_id_does_not_cross_wire_cancellation_state() {
9164 let registry = RequestCancellationRegistry::new();
9165 let id = RequestId::Str("dup".into());
9166
9167 // A protocol-violating peer reuses an in-flight request ID.
9168 let first = registry.register(&id);
9169 let first_marker = first.cancellation();
9170 let second = registry.register(&id);
9171 let second_marker = second.cancellation();
9172
9173 // A cancellation targets whichever request currently owns the ID.
9174 assert!(registry.cancel(&id));
9175 assert!(second_marker.is_cancelled());
9176 assert!(
9177 !first_marker.is_cancelled(),
9178 "the stale request must not observe the newer request's cancellation"
9179 );
9180
9181 // The stale responder must hand out detached markers, not the
9182 // newer request's marker.
9183 assert!(!first.cancellation().is_cancelled());
9184
9185 // Dropping the stale responder must not remove the newer
9186 // request's slot.
9187 drop(first);
9188 assert!(registry.cancel(&id), "newer slot should still be present");
9189
9190 drop(second);
9191 assert!(!registry.cancel(&id), "slot should be removed on drop");
9192 }
9193 }
9194}