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