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}