Skip to main content

ConnectionTo

Struct ConnectionTo 

Source
pub struct ConnectionTo<Counterpart: Role> { /* private fields */ }
Expand description

Connection context for sending messages and spawning tasks.

This is the primary handle for interacting with the JSON-RPC connection from within handler callbacks. You can use it to:

  • Send requests and notifications to the other side
  • Spawn concurrent tasks that run alongside the connection
  • Respond to requests (via Responder which wraps this)

§Cloning

ConnectionTo is cheaply cloneable - all clones refer to the same underlying connection. This makes it easy to share across async tasks.

§Event Loop and Concurrency

Handler callbacks run on the event loop, which means the connection cannot process new messages while your handler is running. Use spawn to offload any expensive or blocking work to concurrent tasks.

See the Event Loop and Concurrency section for more details.

Implementations§

Source§

impl<Counterpart: Role> ConnectionTo<Counterpart>

Source

pub fn counterpart(&self) -> Counterpart

Return the counterpart role this connection is talking to.

Source

pub async fn incoming_closed(&self)

Wait until the incoming transport reaches clean EOF.

Transport closure means that no more messages or responses can arrive. Pending requests are failed first; this completes after registered Builder::on_close callbacks finish. It does not automatically cancel the future passed to Builder::connect_with; use Builder::on_close when the connection should run application-specific cleanup or terminate that future.

This never completes if the connection ends some other way; use terminated to observe every termination.

Source

pub fn is_incoming_closed(&self) -> bool

Return whether clean incoming-EOF processing has completed.

This remains false while Builder::on_close callbacks are running.

Source

pub async fn terminated(&self) -> ConnectionTermination

Wait until the connection driver has terminated, for any reason.

This is the universal terminal event for a connection driven by Builder::connect_to or Builder::connect_with. Unlike incoming_closed, it also completes after transport errors, handler or close-callback errors, foreground completion, and a dropped driver future.

The termination is published once, synchronously, as the driver future returns (or is dropped). At that point:

  • No further handler, close callback, runner, or spawned task of this connection will run; each has completed or been dropped.
  • Every request still awaiting a response has been failed. Requests failed earlier by clean incoming EOF satisfy is_incoming_transport_closed; the rest satisfy is_connection_terminated, as do requests sent afterwards.

Do not await this from inside the connection’s own handlers, close callbacks, or tasks: termination happens only after they finish.

Termination does not reap child processes owned by the transport, nor drain application-owned queues.

Source

pub fn termination(&self) -> Option<ConnectionTermination>

Return the termination reason if the connection driver has terminated.

See terminated.

Source

pub fn spawn( &self, task: impl IntoFuture<Output = Result<(), Error>, IntoFuture: Send + 'static>, ) -> Result<(), Error>

Spawns a task that will run so long as the JSON-RPC connection is being served.

This is the primary mechanism for offloading expensive work from handler callbacks to avoid blocking the event loop. Spawned tasks run concurrently with the connection, allowing the server to continue processing messages.

§Event Loop

Handler callbacks run on the event loop, which cannot process new messages while your handler is running. Use spawn for any expensive operations:

connection.on_receive_request(async |req: ProcessRequest, responder, cx| {
    // Clone cx for the spawned task
    cx.spawn({
        let connection = cx.clone();
        async move {
            let result = expensive_operation(&req.data).await?;
            connection.send_notification(ProcessComplete { result })?;
            Ok(())
        }
    })?;

    // Respond immediately
    responder.respond(ProcessResponse { result: "started".into() })
}, agent_client_protocol::on_receive_request!())
§Errors

If the spawned task returns an error, the entire server will shut down.

Source

pub fn spawn_connection<R: Role, P: Protocol>( &self, builder: Builder<R, impl HandleDispatchFrom<R::Counterpart> + 'static, impl RunWithConnectionTo<R::Counterpart> + 'static, impl HandleConnectionClose<R::Counterpart> + 'static, P>, transport: impl ConnectTo<R> + 'static, ) -> Result<P::Connection<R::Counterpart>, Error>

Spawn a JSON-RPC connection in the background and return the connection context its builder selects.

This is useful for creating multiple connections that communicate with each other, such as implementing proxy patterns or connecting to multiple backend services.

§Arguments
  • builder: The connection builder with handlers configured
  • transport: The transport component to connect to
§Returns

The connection type that builder’s callbacks receive: a raw ConnectionTo for a Raw builder such as Builder::new, a v1::ConnectionTo for Client.v1(), and a v2::ConnectionTo for Client.v2(). Convert a typed connection back with into_raw.

§Example: Proxying to a backend connection
// Set up a backend connection builder
let backend = UntypedRole.builder()
    .on_receive_request(async |req: MyRequest, responder, _cx| {
        responder.respond(MyResponse { status: "ok".into() })
    }, agent_client_protocol::on_receive_request!());

// Spawn it and get a context to send requests to it
let backend_connection = cx.spawn_connection(backend, MockTransport)?;

// Now you can forward requests to the backend
let response = backend_connection.send_request(MyRequest {}).block_task().await?;
§Example: Spawning a v1 client
let child: v1::ConnectionTo<Agent> = connection.spawn_connection(Client.v1(), transport)?;
Source

pub fn protocol_version(&self) -> Option<ProtocolVersion>

The ACP protocol version of this connection’s wire guard, or None when its builder applies no guard, as with Builder::new.

Client.v1(), Agent.v1() and Proxy.v1() connections report ProtocolVersion::V1, and the v2() entry points report ProtocolVersion::V2.

Source

pub fn into_protocol<P: Protocol>( self, ) -> Result<P::Connection<Counterpart>, Error>

The same connection, typed for protocol P.

into_protocol::<V1>() returns a v1::ConnectionTo (and, with unstable_protocol_v2, into_protocol::<V2>() a v2::ConnectionTo), whose sends return request handles typed for that version and which carries that version’s session helpers. Only the Rust API changes: the connection’s wire guard is the one its builder selected. The versioned connections convert back with into_raw.

§Errors

Returns an Internal error (-32603) when the builder’s guard selected a different version than P, for example into_protocol::<V2>() on a connection from Client.v1(). The mismatch is a local programming error, not a malformed message from the peer. A connection whose builder applies no guard (see protocol_version) converts to every protocol, and into_protocol::<Raw>() never fails.

fn sessions(connection: ConnectionTo<Agent>) -> Result<v1::ConnectionTo<Agent>, Error> {
    connection.into_protocol::<V1>()
}
Source

pub fn send_proxied_message<Req: JsonRpcRequest<Response: Send>, Notif: JsonRpcNotification, Q: Protocol>( &self, message: Dispatch<Req, Notif, Q>, ) -> Result<(), Error>
where Counterpart: HasPeer<Counterpart>,

Send a request/notification and forward the response appropriately.

The request context’s response type matches the request’s response type, enabling type-safe message forwarding.

Source

pub fn send_proxied_message_to<Peer: Role, Req: JsonRpcRequest<Response: Send>, Notif: JsonRpcNotification, Q: Protocol>( &self, peer: Peer, message: Dispatch<Req, Notif, Q>, ) -> Result<(), Error>
where Counterpart: HasPeer<Peer>,

Send a request/notification and forward the response appropriately.

The request context’s response type matches the request’s response type, enabling type-safe message forwarding.

$/cancel_request notifications are not forwarded: their requestId refers to a request on the connection they arrived over and would be meaningless to peer. Cancellation instead propagates hop by hop, because the responders passed to forward_response_to observe it and re-issue the cancellation with the forwarded request’s own ID.

Source

pub fn send_request<Req: JsonRpcRequest>( &self, request: Req, ) -> SentRequest<Req::Response>
where Counterpart: HasPeer<Counterpart>,

Send an outgoing request and return a SentRequest for handling the reply.

The returned SentRequest makes the response-consumption mode explicit:

  • on_receiving_result - Register a callback and return immediately. If registered before the response is routed during its original dispatch, the loop waits for the callback to complete.
  • block_task - Wait on the current task until the response arrives. This is only safe when that task already runs outside the dispatch loop.

For callback ordering selected before publication, use prepare_request instead. Even an immediately chained callback can race with a fast response on a concurrent connection.

§Anti-Footgun Design

The API intentionally makes it difficult to block on the result directly to prevent the common mistake of blocking the event loop while waiting for a response:

ⓘ
// ❌ This doesn't compile - prevents blocking the event loop
let response = cx.send_request(MyRequest {}).await?;
// ✅ Option 1: Register an ordered callback (safe in handlers)
cx.send_request(MyRequest {})
    .on_receiving_result(async |result| {
        // Handle the response
        Ok(())
    })?;

// ✅ Option 2: Block in spawned task (safe because task is concurrent)
cx.spawn({
    let cx = cx.clone();
    async move {
        let response = cx.send_request(MyRequest {})
            .block_task()
            .await?;
        // Process response...
        Ok(())
    }
})?;

Send an outgoing request to the default counterpart peer.

This is a convenience method that sends to the counterpart role R. For explicit control over the target peer, use send_request_to.

Source

pub fn send_request_to<Peer: Role, Req: JsonRpcRequest>( &self, peer: Peer, request: Req, ) -> SentRequest<Req::Response>
where Counterpart: HasPeer<Peer>,

Send an outgoing request to a specific peer.

The message will be transformed according to the HasPeer implementation before being sent.

Source

pub fn prepare_request<Req: JsonRpcRequest>( &self, request: Req, ) -> PreparedRequest<Req::Response>
where Counterpart: HasPeer<Counterpart>,

Prepare a request without sending it until response handling is selected.

Unlike send_request, this does not register a pending reply or enqueue the request. A consuming method on the returned PreparedRequest publishes it synchronously. Callback-style methods select ordered consumption before publication, closing the race with a fast peer response even when the connection runs on another task.

Dropping the prepared request sends nothing. See PreparedRequest for the available consumption modes and their error and cancellation behavior.

§Examples
connection.prepare_request(MyRequest {}).on_receiving_result(async |result| {
    let response = result?;
    // Apply bounded response work before later inbound messages.
    Ok(())
})?;
Source

pub fn prepare_request_to<Peer: Role, Req: JsonRpcRequest>( &self, peer: Peer, request: Req, ) -> PreparedRequest<Req::Response>
where Counterpart: HasPeer<Peer>,

Prepare a request to a specific peer without sending it.

The request is serialized now. A consuming method synchronously registers its pending reply and enqueues the request; peer transformation and transmission happen later in the connection driver. See prepare_request.

Source

pub fn send_notification<N: JsonRpcNotification>( &self, notification: N, ) -> Result<(), Error>
where Counterpart: HasPeer<Counterpart>,

Send an outgoing notification to the default counterpart peer (no reply expected).

Notifications are fire-and-forget messages that don’t have IDs and don’t expect responses. This method sends the notification immediately and returns.

This is a convenience method that sends to the counterpart role R. For explicit control over the target peer, use send_notification_to.

cx.send_notification(StatusUpdate {
    message: "Processing...".into(),
})?;
Source

pub fn send_notification_to<Peer: Role, N: JsonRpcNotification>( &self, peer: Peer, notification: N, ) -> Result<(), Error>
where Counterpart: HasPeer<Peer>,

Send an outgoing notification to a specific peer (no reply expected).

The message will be transformed according to the HasPeer implementation before being sent.

Source

pub fn send_cancel_request( &self, request_id: impl Into<RawJsonRpcId>, ) -> Result<(), Error>
where Counterpart: HasPeer<Counterpart>,

Send a $/cancel_request notification for an arbitrary request ID to the default counterpart peer.

Prefer SentRequest::cancel when you have the request handle: it already knows the correct request ID. Use this low-level method only when implementing custom routing with a request ID that is valid on this connection.

Source

pub fn send_cancel_request_to<Peer: Role>( &self, _peer: Peer, request_id: impl Into<RawJsonRpcId>, ) -> Result<(), Error>
where Counterpart: HasPeer<Peer>,

Send a $/cancel_request notification for an arbitrary request ID to a specific peer.

$/cancel_request is hop-local, so the notification is always sent plain, never wrapped in proxy/successor, even to a proxy’s successor. To cancel a request sent to a successor, pass the ID of the proxy/successor request on this connection.

Prefer SentRequest::cancel when you have the request handle: it already knows the correct request ID. Use this low-level method only when implementing custom routing with a request ID that is valid on this connection.

Source

pub fn add_dynamic_handler( &self, handler: impl HandleDispatchFrom<Counterpart> + 'static, ) -> Result<DynamicHandlerGuard<Counterpart>, Error>

Register a dynamic message handler, used to intercept messages specific to a particular session or some similar modal thing.

Dynamic message handlers run after the handlers registered on Builder and before the role’s default handler. They receive messages that the builder handlers decline.

The handler will stay registered until the returned registration guard is dropped.

Trait Implementations§

Source§

impl<Counterpart: Clone + Role> Clone for ConnectionTo<Counterpart>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<Counterpart: Debug + Role> Debug for ConnectionTo<Counterpart>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<Counterpart: Role> From<ConnectionTo<Counterpart>> for ConnectionTo<Counterpart>

Source§

fn from(connection: ConnectionTo<Counterpart>) -> Self

The same as ConnectionTo::into_raw. Generic code over Protocol uses it to turn a P::Connection<Counterpart> back into the version-neutral connection.

Source§

impl<Counterpart: Role> From<ConnectionTo<Counterpart>> for ConnectionTo<Counterpart>

Available on crate feature unstable_protocol_v2 only.
Source§

fn from(connection: ConnectionTo<Counterpart>) -> Self

The same as ConnectionTo::into_raw. Generic code over Protocol uses it to turn a P::Connection<Counterpart> back into the version-neutral connection.

Auto Trait Implementations§

§

impl<Counterpart> !RefUnwindSafe for ConnectionTo<Counterpart>

§

impl<Counterpart> !UnwindSafe for ConnectionTo<Counterpart>

§

impl<Counterpart> Freeze for ConnectionTo<Counterpart>
where Counterpart: Freeze, UnboundedSender<DynamicHandlerMessage<Counterpart>>: Freeze,

§

impl<Counterpart> Send for ConnectionTo<Counterpart>
where UnboundedSender<DynamicHandlerMessage<Counterpart>>: Send,

§

impl<Counterpart> Sync for ConnectionTo<Counterpart>
where UnboundedSender<DynamicHandlerMessage<Counterpart>>: Sync,

§

impl<Counterpart> Unpin for ConnectionTo<Counterpart>
where Counterpart: Unpin, UnboundedSender<DynamicHandlerMessage<Counterpart>>: Unpin,

§

impl<Counterpart> UnsafeUnpin for ConnectionTo<Counterpart>
where Counterpart: UnsafeUnpin, UnboundedSender<DynamicHandlerMessage<Counterpart>>: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoMaybeUndefined<T> for T

Source§

fn into_maybe_undefined(self) -> MaybeUndefined<T>

Converts this value into a three-state builder argument.
Source§

impl<T> IntoOption<T> for T

Source§

fn into_option(self) -> Option<T>

Converts this value into an optional builder argument.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more