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