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