Skip to main content

agent_client_protocol/jsonrpc/
v1_connection.rs

1//! The protocol-v1 connection type, `agent_client_protocol::v1::ConnectionTo`.
2
3use super::ConnectionTo as RawConnectionTo;
4use super::{
5    Builder, ConnectTo, ConnectionTermination, Dispatch, DynamicHandlerGuard,
6    HandleConnectionClose, HandleDispatchFrom, JsonRpcNotification, JsonRpcRequest,
7    PreparedRequest, Protocol, SentRequest, V1, run::RunWithConnectionTo,
8};
9use crate::role::{HasPeer, Role};
10
11/// A protocol-v1 connection context.
12///
13/// Values of this type are supplied to callbacks registered on a
14/// [`v1::Builder`](crate::v1::Builder). It exposes the general connection
15/// operations, typed for protocol v1, and the v1 session helpers such as
16/// [`build_session`](Self::build_session). Requests sent through it return
17/// [`v1::SentRequest`](crate::v1::SentRequest) and
18/// [`v1::PreparedRequest`](crate::v1::PreparedRequest), whose `id()` is a
19/// [`v1::schema::RequestId`](crate::v1::schema::RequestId). The generic
20/// JSON-RPC send methods remain schema-agnostic.
21///
22/// This is a thin, cheaply cloneable handle to the underlying JSON-RPC
23/// connection. It intentionally does not implement [`Deref`](std::ops::Deref)
24/// to the version-neutral [`ConnectionTo`](crate::ConnectionTo); convert
25/// explicitly with [`into_raw`](Self::into_raw) and
26/// [`ConnectionTo::into_protocol`](crate::ConnectionTo::into_protocol).
27#[derive(Clone, Debug)]
28pub struct ConnectionTo<Counterpart: Role> {
29    pub(crate) inner: RawConnectionTo<Counterpart>,
30}
31
32impl<Counterpart: Role> From<ConnectionTo<Counterpart>> for RawConnectionTo<Counterpart> {
33    /// The same as [`ConnectionTo::into_raw`]. Generic code over
34    /// [`Protocol`] uses it to turn a `P::Connection<Counterpart>` back into
35    /// the version-neutral connection.
36    fn from(connection: ConnectionTo<Counterpart>) -> Self {
37        connection.into_raw()
38    }
39}
40
41impl<Counterpart: Role> ConnectionTo<Counterpart> {
42    /// Access the underlying version-neutral connection inside the SDK.
43    pub(crate) fn raw_connection(&self) -> &RawConnectionTo<Counterpart> {
44        &self.inner
45    }
46
47    /// The underlying version-neutral connection.
48    ///
49    /// Requests sent through it return [`Raw`](crate::Raw) handles. Convert
50    /// back with [`ConnectionTo::into_protocol`](crate::ConnectionTo::into_protocol).
51    #[must_use]
52    pub fn into_raw(self) -> RawConnectionTo<Counterpart> {
53        self.inner
54    }
55
56    /// Return the counterpart role this connection is talking to.
57    pub fn counterpart(&self) -> Counterpart {
58        self.inner.counterpart()
59    }
60
61    /// Wait until the incoming transport reaches clean EOF.
62    pub async fn incoming_closed(&self) {
63        self.inner.incoming_closed().await;
64    }
65
66    /// Return whether clean incoming-EOF processing has completed.
67    #[must_use]
68    pub fn is_incoming_closed(&self) -> bool {
69        self.inner.is_incoming_closed()
70    }
71
72    /// Wait until the connection driver has terminated, for any reason.
73    ///
74    /// See [`ConnectionTo::terminated`](crate::ConnectionTo::terminated).
75    pub async fn terminated(&self) -> ConnectionTermination {
76        self.inner.terminated().await
77    }
78
79    /// Return the termination reason if the connection driver has terminated.
80    #[must_use]
81    pub fn termination(&self) -> Option<ConnectionTermination> {
82        self.inner.termination()
83    }
84
85    /// Spawn a task that runs for as long as the JSON-RPC connection is served.
86    #[track_caller]
87    pub fn spawn(
88        &self,
89        task: impl IntoFuture<Output = Result<(), crate::Error>, IntoFuture: Send + 'static>,
90    ) -> Result<(), crate::Error> {
91        self.inner.spawn(task)
92    }
93
94    /// Spawn a JSON-RPC connection in the background.
95    ///
96    /// The returned connection context is selected by `builder`; spawning a
97    /// [`v1::Builder`](crate::v1::Builder) therefore returns another
98    /// [`v1::ConnectionTo`](ConnectionTo).
99    ///
100    /// ```no_run
101    /// # use agent_client_protocol::{
102    /// #     Agent, Client, ConnectTo, Error,
103    /// # };
104    /// # use agent_client_protocol::v1::ConnectionTo;
105    /// # fn example(
106    /// #     connection: ConnectionTo<Agent>,
107    /// #     transport: impl ConnectTo<Client> + 'static,
108    /// # ) -> Result<(), Error> {
109    /// let child: ConnectionTo<Agent> =
110    ///     connection.spawn_connection(Client.v1(), transport)?;
111    /// # drop(child);
112    /// # Ok(())
113    /// # }
114    /// ```
115    #[track_caller]
116    pub fn spawn_connection<R: Role, P: Protocol>(
117        &self,
118        builder: Builder<
119            R,
120            impl HandleDispatchFrom<R::Counterpart> + 'static,
121            impl RunWithConnectionTo<R::Counterpart> + 'static,
122            impl HandleConnectionClose<R::Counterpart> + 'static,
123            P,
124        >,
125        transport: impl ConnectTo<R> + 'static,
126    ) -> Result<P::Connection<R::Counterpart>, crate::Error> {
127        self.inner.spawn_connection(builder, transport)
128    }
129
130    /// Send a request or notification and forward its response appropriately.
131    pub fn send_proxied_message<
132        Req: JsonRpcRequest<Response: Send>,
133        Notif: JsonRpcNotification,
134        Q: Protocol,
135    >(
136        &self,
137        message: Dispatch<Req, Notif, Q>,
138    ) -> Result<(), crate::Error>
139    where
140        Counterpart: HasPeer<Counterpart>,
141    {
142        self.inner.send_proxied_message(message)
143    }
144
145    /// Send a request or notification to a specific peer and forward its
146    /// response appropriately.
147    pub fn send_proxied_message_to<
148        Peer: Role,
149        Req: JsonRpcRequest<Response: Send>,
150        Notif: JsonRpcNotification,
151        Q: Protocol,
152    >(
153        &self,
154        peer: Peer,
155        message: Dispatch<Req, Notif, Q>,
156    ) -> Result<(), crate::Error>
157    where
158        Counterpart: HasPeer<Peer>,
159    {
160        self.inner.send_proxied_message_to(peer, message)
161    }
162
163    /// Send an outgoing request to the default counterpart peer.
164    pub fn send_request<Req: JsonRpcRequest>(&self, request: Req) -> SentRequest<Req::Response, V1>
165    where
166        Counterpart: HasPeer<Counterpart>,
167    {
168        self.send_request_to(self.inner.counterpart(), request)
169    }
170
171    /// Send an outgoing request to a specific peer.
172    pub fn send_request_to<Peer: Role, Req: JsonRpcRequest>(
173        &self,
174        peer: Peer,
175        request: Req,
176    ) -> SentRequest<Req::Response, V1>
177    where
178        Counterpart: HasPeer<Peer>,
179    {
180        self.inner
181            .send_request_to_with_options(peer, request, false, None, None)
182    }
183
184    /// Prepare a request without sending it until response handling is selected.
185    ///
186    /// See [`ConnectionTo::prepare_request`](crate::ConnectionTo::prepare_request) for publication and ordering semantics.
187    pub fn prepare_request<Req: JsonRpcRequest>(
188        &self,
189        request: Req,
190    ) -> PreparedRequest<Req::Response, V1>
191    where
192        Counterpart: HasPeer<Counterpart>,
193    {
194        self.prepare_request_to(self.inner.counterpart(), request)
195    }
196
197    /// Prepare a request to a specific peer without sending it.
198    ///
199    /// See [`ConnectionTo::prepare_request_to`](crate::ConnectionTo::prepare_request_to) for publication and ordering semantics.
200    pub fn prepare_request_to<Peer: Role, Req: JsonRpcRequest>(
201        &self,
202        peer: Peer,
203        request: Req,
204    ) -> PreparedRequest<Req::Response, V1>
205    where
206        Counterpart: HasPeer<Peer>,
207    {
208        self.inner
209            .prepare_request_to_with_options(peer, request, None, None)
210    }
211
212    /// Send an outgoing notification to the default counterpart peer.
213    pub fn send_notification<N: JsonRpcNotification>(
214        &self,
215        notification: N,
216    ) -> Result<(), crate::Error>
217    where
218        Counterpart: HasPeer<Counterpart>,
219    {
220        self.inner.send_notification(notification)
221    }
222
223    /// Send an outgoing notification to a specific peer.
224    pub fn send_notification_to<Peer: Role, N: JsonRpcNotification>(
225        &self,
226        peer: Peer,
227        notification: N,
228    ) -> Result<(), crate::Error>
229    where
230        Counterpart: HasPeer<Peer>,
231    {
232        self.inner.send_notification_to(peer, notification)
233    }
234
235    /// Send a `$/cancel_request` notification to the default counterpart peer.
236    ///
237    /// Prefer [`SentRequest::cancel`](crate::SentRequest::cancel) when you
238    /// have the request handle. `request_id` is a v1 request ID, such as the
239    /// [`id`](crate::SentRequest::id) of a request sent through this
240    /// connection. The raw [`ConnectionTo::send_cancel_request`](crate::ConnectionTo::send_cancel_request)
241    /// takes any [`RawJsonRpcId`](crate::RawJsonRpcId).
242    pub fn send_cancel_request(
243        &self,
244        request_id: impl Into<crate::v1::schema::RequestId>,
245    ) -> Result<(), crate::Error>
246    where
247        Counterpart: HasPeer<Counterpart>,
248    {
249        self.inner.send_cancel_request(request_id.into())
250    }
251
252    /// Send a `$/cancel_request` notification to a specific peer.
253    ///
254    /// The notification is always sent plain, never wrapped in
255    /// `proxy/successor`, as the raw
256    /// [`ConnectionTo::send_cancel_request_to`](crate::ConnectionTo::send_cancel_request_to)
257    /// describes. See [`send_cancel_request`](Self::send_cancel_request).
258    pub fn send_cancel_request_to<Peer: Role>(
259        &self,
260        peer: Peer,
261        request_id: impl Into<crate::v1::schema::RequestId>,
262    ) -> Result<(), crate::Error>
263    where
264        Counterpart: HasPeer<Peer>,
265    {
266        self.inner.send_cancel_request_to(peer, request_id.into())
267    }
268
269    /// Register a low-level dynamic message handler.
270    ///
271    /// Dynamic handlers use the version-neutral [`HandleDispatchFrom`] trait,
272    /// so their callback receives the underlying version-neutral
273    /// [`ConnectionTo`](crate::ConnectionTo). Prefer typed handlers on
274    /// [`v1::Builder`](crate::v1::Builder) when registration can happen before the
275    /// connection starts.
276    ///
277    /// ```no_run
278    /// # use agent_client_protocol::{
279    /// #     Agent, Client, ConnectTo, DynamicHandlerGuard, Error, NullHandler,
280    /// # };
281    /// # async fn example(
282    /// #     transport: impl ConnectTo<Client> + 'static,
283    /// # ) -> Result<(), Error> {
284    /// Client.v1().connect_with(transport, async |connection| {
285    ///     let guard: DynamicHandlerGuard<Agent> =
286    ///         connection.add_dynamic_handler(NullHandler)?;
287    ///
288    ///     // Keep `guard` alive for as long as the modal handler is needed.
289    ///     drop(guard);
290    ///     Ok(())
291    /// }).await
292    /// # }
293    /// ```
294    pub fn add_dynamic_handler(
295        &self,
296        handler: impl HandleDispatchFrom<Counterpart> + 'static,
297    ) -> Result<DynamicHandlerGuard<Counterpart>, crate::Error> {
298        self.inner.add_dynamic_handler(handler)
299    }
300}