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