Skip to main content

agent_client_protocol/jsonrpc/
v2_connection.rs

1//! The protocol-v2 connection type, `agent_client_protocol::v2::ConnectionTo`.
2
3use super::ConnectionTo as RawConnectionTo;
4use super::{
5    Builder, ConnectTo, ConnectionTermination, Dispatch, DynamicHandlerGuard,
6    HandleConnectionClose, HandleDispatchFrom, JsonRpcNotification, JsonRpcRequest,
7    PreparedRequest, Protocol, SentRequest, V2, protocol_compat, run::RunWithConnectionTo,
8};
9use crate::Agent;
10use crate::role::{HasPeer, Role};
11use crate::v2::InitializeOutcome;
12
13/// A protocol-v2 connection context.
14///
15/// Values of this type are supplied to callbacks registered on a
16/// [`v2::Builder`](crate::v2::Builder). It exposes the general connection operations that are valid
17/// for protocol v2 while keeping version-specific high-level helpers for other
18/// protocol versions out of the typed context. The generic JSON-RPC send
19/// methods remain intentionally schema-agnostic.
20///
21/// This is a thin, cheaply cloneable handle to the underlying JSON-RPC
22/// connection. It intentionally does not implement [`Deref`](std::ops::Deref)
23/// to the version-neutral [`ConnectionTo`](crate::ConnectionTo); convert
24/// explicitly with [`into_raw`](Self::into_raw) and
25/// [`ConnectionTo::into_protocol`](crate::ConnectionTo::into_protocol).
26#[derive(Clone, Debug)]
27pub struct ConnectionTo<Counterpart: Role> {
28    pub(crate) inner: RawConnectionTo<Counterpart>,
29}
30
31impl ConnectionTo<Agent> {
32    /// Send the client's `initialize` request and report how the agent
33    /// negotiated.
34    ///
35    /// Unlike [`send_request`](Self::send_request), a v1-only agent's valid v1
36    /// response resolves successfully as [`InitializeOutcome::LowerVersion`]
37    /// with the agent's typed response, so the caller can decide whether to
38    /// fall back to a v1 client implementation. The connection stays
39    /// uninitialized in that case; the SDK never converts it to v1.
40    ///
41    /// The request still resolves with an error for errors returned by the
42    /// agent (including the `-32022` version mismatch), malformed responses,
43    /// and transport failures.
44    ///
45    /// After an initialization completes, `initialize` may be sent again
46    /// before any other request; the agent treats the latest one as
47    /// authoritative. Once another request has been sent, a repeat resolves
48    /// with an `-32600` error without being sent.
49    ///
50    /// ```no_run
51    /// # use agent_client_protocol::{schema::ProtocolVersion, v2::schema};
52    /// # use agent_client_protocol::{Client, ConnectTo};
53    /// use agent_client_protocol::SelectedInitializeResponse;
54    /// use agent_client_protocol::v2::InitializeOutcome;
55    /// # async fn example(agent: impl ConnectTo<Client>) -> agent_client_protocol::Result<()> {
56    /// let outcome = Client
57    ///     .v2()
58    ///     .connect_with(agent, async |cx| {
59    ///         let request = schema::InitializeRequest::new(
60    ///             ProtocolVersion::V2,
61    ///             schema::Implementation::new("example", "0.1.0"),
62    ///         );
63    ///         match cx.send_initialize(request).block_task().await? {
64    ///             InitializeOutcome::Initialized(_response) => {
65    ///                 // Continue with v2 traffic.
66    ///                 Ok(None)
67    ///             }
68    ///             InitializeOutcome::LowerVersion(mismatch) => Ok(Some(mismatch)),
69    ///             _ => Err(agent_client_protocol::Error::internal_error().data("unexpected outcome")),
70    ///         }
71    ///     })
72    ///     .await?;
73    /// if let Some(mismatch) = outcome
74    ///     && let SelectedInitializeResponse::V1(_v1_response) = mismatch.response
75    /// {
76    ///     // Reconnect with a v1 client implementation.
77    /// }
78    /// # Ok(())
79    /// # }
80    /// ```
81    pub fn send_initialize(
82        &self,
83        request: crate::v2::schema::InitializeRequest,
84    ) -> SentRequest<InitializeOutcome, V2> {
85        let mut prepared = self.inner.prepare_request_to_with_options::<V2, _, _>(
86            self.inner.counterpart(),
87            protocol_compat::InitializeNegotiation(request),
88            None,
89            None,
90        );
91        prepared
92            .publication
93            .pending_reply
94            .accepts_lower_initialize_version = true;
95        prepared.into_sent_request(false)
96    }
97}
98
99impl<Counterpart: Role> From<ConnectionTo<Counterpart>> for RawConnectionTo<Counterpart> {
100    /// The same as [`ConnectionTo::into_raw`]. Generic code over
101    /// [`Protocol`] uses it to turn a `P::Connection<Counterpart>` back into
102    /// the version-neutral connection.
103    fn from(connection: ConnectionTo<Counterpart>) -> Self {
104        connection.into_raw()
105    }
106}
107
108impl<Counterpart: Role> ConnectionTo<Counterpart> {
109    /// Access the underlying version-neutral connection inside the SDK.
110    pub(crate) fn raw_connection(&self) -> &RawConnectionTo<Counterpart> {
111        &self.inner
112    }
113
114    /// The underlying version-neutral connection.
115    ///
116    /// Requests sent through it return [`Raw`](crate::Raw) handles. Convert
117    /// back with [`ConnectionTo::into_protocol`](crate::ConnectionTo::into_protocol).
118    #[must_use]
119    pub fn into_raw(self) -> RawConnectionTo<Counterpart> {
120        self.inner
121    }
122
123    /// Return the counterpart role this connection is talking to.
124    pub fn counterpart(&self) -> Counterpart {
125        self.inner.counterpart()
126    }
127
128    /// Wait until the incoming transport reaches clean EOF.
129    pub async fn incoming_closed(&self) {
130        self.inner.incoming_closed().await;
131    }
132
133    /// Return whether clean incoming-EOF processing has completed.
134    #[must_use]
135    pub fn is_incoming_closed(&self) -> bool {
136        self.inner.is_incoming_closed()
137    }
138
139    /// Wait until the connection driver has terminated, for any reason.
140    ///
141    /// See [`ConnectionTo::terminated`](crate::ConnectionTo::terminated).
142    pub async fn terminated(&self) -> ConnectionTermination {
143        self.inner.terminated().await
144    }
145
146    /// Return the termination reason if the connection driver has terminated.
147    #[must_use]
148    pub fn termination(&self) -> Option<ConnectionTermination> {
149        self.inner.termination()
150    }
151
152    /// Spawn a task that runs for as long as the JSON-RPC connection is served.
153    #[track_caller]
154    pub fn spawn(
155        &self,
156        task: impl IntoFuture<Output = Result<(), crate::Error>, IntoFuture: Send + 'static>,
157    ) -> Result<(), crate::Error> {
158        self.inner.spawn(task)
159    }
160
161    /// Spawn a JSON-RPC connection in the background.
162    ///
163    /// The returned connection context is selected by `builder`; spawning a
164    /// [`v2::Builder`](crate::v2::Builder) therefore returns another
165    /// [`v2::ConnectionTo`](ConnectionTo).
166    ///
167    /// ```no_run
168    /// # use agent_client_protocol::{
169    /// #     Agent, Client, ConnectTo, Error,
170    /// # };
171    /// # use agent_client_protocol::v2::ConnectionTo;
172    /// # fn example(
173    /// #     connection: ConnectionTo<Agent>,
174    /// #     transport: impl ConnectTo<Client> + 'static,
175    /// # ) -> Result<(), Error> {
176    /// let child: ConnectionTo<Agent> =
177    ///     connection.spawn_connection(Client.v2(), transport)?;
178    /// # drop(child);
179    /// # Ok(())
180    /// # }
181    /// ```
182    #[track_caller]
183    pub fn spawn_connection<R: Role, P: Protocol>(
184        &self,
185        builder: Builder<
186            R,
187            impl HandleDispatchFrom<R::Counterpart> + 'static,
188            impl RunWithConnectionTo<R::Counterpart> + 'static,
189            impl HandleConnectionClose<R::Counterpart> + 'static,
190            P,
191        >,
192        transport: impl ConnectTo<R> + 'static,
193    ) -> Result<P::Connection<R::Counterpart>, crate::Error> {
194        self.inner.spawn_connection(builder, transport)
195    }
196
197    /// Send a request or notification and forward its response appropriately.
198    pub fn send_proxied_message<
199        Req: JsonRpcRequest<Response: Send>,
200        Notif: JsonRpcNotification,
201        Q: Protocol,
202    >(
203        &self,
204        message: Dispatch<Req, Notif, Q>,
205    ) -> Result<(), crate::Error>
206    where
207        Counterpart: HasPeer<Counterpart>,
208    {
209        self.inner.send_proxied_message(message)
210    }
211
212    /// Send a request or notification to a specific peer and forward its
213    /// response appropriately.
214    pub fn send_proxied_message_to<
215        Peer: Role,
216        Req: JsonRpcRequest<Response: Send>,
217        Notif: JsonRpcNotification,
218        Q: Protocol,
219    >(
220        &self,
221        peer: Peer,
222        message: Dispatch<Req, Notif, Q>,
223    ) -> Result<(), crate::Error>
224    where
225        Counterpart: HasPeer<Peer>,
226    {
227        self.inner.send_proxied_message_to(peer, message)
228    }
229
230    /// Send an outgoing request to the default counterpart peer.
231    pub fn send_request<Req: JsonRpcRequest>(&self, request: Req) -> SentRequest<Req::Response, V2>
232    where
233        Counterpart: HasPeer<Counterpart>,
234    {
235        self.send_request_to(self.inner.counterpart(), request)
236    }
237
238    /// Send an outgoing request to a specific peer.
239    pub fn send_request_to<Peer: Role, Req: JsonRpcRequest>(
240        &self,
241        peer: Peer,
242        request: Req,
243    ) -> SentRequest<Req::Response, V2>
244    where
245        Counterpart: HasPeer<Peer>,
246    {
247        self.inner
248            .send_request_to_with_options(peer, request, false, None, None)
249    }
250
251    /// Prepare a request without sending it until response handling is selected.
252    ///
253    /// See [`ConnectionTo::prepare_request`](crate::ConnectionTo::prepare_request) for publication and ordering semantics.
254    pub fn prepare_request<Req: JsonRpcRequest>(
255        &self,
256        request: Req,
257    ) -> PreparedRequest<Req::Response, V2>
258    where
259        Counterpart: HasPeer<Counterpart>,
260    {
261        self.prepare_request_to(self.inner.counterpart(), request)
262    }
263
264    /// Prepare a request to a specific peer without sending it.
265    ///
266    /// See [`ConnectionTo::prepare_request_to`](crate::ConnectionTo::prepare_request_to) for publication and ordering semantics.
267    pub fn prepare_request_to<Peer: Role, Req: JsonRpcRequest>(
268        &self,
269        peer: Peer,
270        request: Req,
271    ) -> PreparedRequest<Req::Response, V2>
272    where
273        Counterpart: HasPeer<Peer>,
274    {
275        self.inner
276            .prepare_request_to_with_options(peer, request, None, None)
277    }
278
279    /// Send an outgoing notification to the default counterpart peer.
280    pub fn send_notification<N: JsonRpcNotification>(
281        &self,
282        notification: N,
283    ) -> Result<(), crate::Error>
284    where
285        Counterpart: HasPeer<Counterpart>,
286    {
287        self.inner.send_notification(notification)
288    }
289
290    /// Send an outgoing notification to a specific peer.
291    pub fn send_notification_to<Peer: Role, N: JsonRpcNotification>(
292        &self,
293        peer: Peer,
294        notification: N,
295    ) -> Result<(), crate::Error>
296    where
297        Counterpart: HasPeer<Peer>,
298    {
299        self.inner.send_notification_to(peer, notification)
300    }
301
302    /// Send a `$/cancel_request` notification to the default counterpart peer.
303    ///
304    /// Prefer [`SentRequest::cancel`](crate::SentRequest::cancel) when you
305    /// have the request handle. `request_id` is a v2 request ID, such as the
306    /// [`id`](crate::SentRequest::id) of a request sent through this
307    /// connection. The raw [`ConnectionTo::send_cancel_request`](crate::ConnectionTo::send_cancel_request)
308    /// takes any [`RawJsonRpcId`](crate::RawJsonRpcId).
309    pub fn send_cancel_request(
310        &self,
311        request_id: impl Into<crate::v2::schema::RequestId>,
312    ) -> Result<(), crate::Error>
313    where
314        Counterpart: HasPeer<Counterpart>,
315    {
316        self.inner.send_cancel_request(request_id.into())
317    }
318
319    /// Send a `$/cancel_request` notification to a specific peer.
320    ///
321    /// The notification is always sent plain, never wrapped in
322    /// `proxy/successor`, as the raw
323    /// [`ConnectionTo::send_cancel_request_to`](crate::ConnectionTo::send_cancel_request_to)
324    /// describes. See [`send_cancel_request`](Self::send_cancel_request).
325    pub fn send_cancel_request_to<Peer: Role>(
326        &self,
327        peer: Peer,
328        request_id: impl Into<crate::v2::schema::RequestId>,
329    ) -> Result<(), crate::Error>
330    where
331        Counterpart: HasPeer<Peer>,
332    {
333        self.inner.send_cancel_request_to(peer, request_id.into())
334    }
335
336    /// Register a low-level dynamic message handler.
337    ///
338    /// Dynamic handlers use the version-neutral [`HandleDispatchFrom`] trait,
339    /// so their callback receives the underlying version-neutral
340    /// [`ConnectionTo`](crate::ConnectionTo). Prefer typed handlers on
341    /// [`v2::Builder`](crate::v2::Builder) when registration can happen before the
342    /// connection starts.
343    ///
344    /// ```no_run
345    /// # use agent_client_protocol::{
346    /// #     Agent, Client, ConnectTo, DynamicHandlerGuard, Error, NullHandler,
347    /// # };
348    /// # async fn example(
349    /// #     transport: impl ConnectTo<Client> + 'static,
350    /// # ) -> Result<(), Error> {
351    /// Client.v2().connect_with(transport, async |connection| {
352    ///     let guard: DynamicHandlerGuard<Agent> =
353    ///         connection.add_dynamic_handler(NullHandler)?;
354    ///
355    ///     // Keep `guard` alive for as long as the modal handler is needed.
356    ///     drop(guard);
357    ///     Ok(())
358    /// }).await
359    /// # }
360    /// ```
361    pub fn add_dynamic_handler(
362        &self,
363        handler: impl HandleDispatchFrom<Counterpart> + 'static,
364    ) -> Result<DynamicHandlerGuard<Counterpart>, crate::Error> {
365        self.inner.add_dynamic_handler(handler)
366    }
367}