Skip to main content

agent_client_protocol/jsonrpc/
protocol.rs

1//! The protocol markers that type request handles and builder callbacks by ACP
2//! version.
3
4use std::fmt::{self, Debug, Display};
5use std::hash::Hash;
6
7use serde::Serialize;
8
9use crate::jsonrpc::{ConnectionTo, RawJsonRpcId};
10use crate::role::Role;
11use crate::v1::schema as v1;
12#[cfg(feature = "unstable_protocol_v2")]
13use crate::v2::schema as v2;
14
15/// The protocol version of a request handle or builder, as a type.
16///
17/// The request handles ([`Responder`](crate::Responder),
18/// [`ResponseRouter`](crate::ResponseRouter), [`SentRequest`](crate::SentRequest),
19/// [`PreparedRequest`](crate::PreparedRequest) and [`Dispatch`](crate::Dispatch))
20/// and the connection [`Builder`](crate::Builder) take a protocol marker as their
21/// last type parameter. It selects the request ID type their `id()` returns and
22/// the connection type builder callbacks receive:
23///
24/// | Marker | `RequestId` | `Connection<Counterpart>` |
25/// | --- | --- | --- |
26/// | [`Raw`] (the default) | [`RawJsonRpcId`] | [`ConnectionTo`] |
27/// | [`V1`] | [`v1::schema::RequestId`](crate::v1::schema::RequestId) | [`v1::ConnectionTo`](crate::v1::ConnectionTo) |
28/// | `V2` (`unstable_protocol_v2`) | `v2::schema::RequestId` | `v2::ConnectionTo` |
29///
30/// - Typed handlers on `Client.v1()`, `Agent.v1()` and
31///   `Proxy.v1()`, and requests sent through `v1::ConnectionTo`, use [`V1`].
32/// - Typed handlers on `Client.v2()`, `Agent.v2()` and `Proxy.v2()`, and
33///   requests sent through `v2::ConnectionTo`, use `V2`.
34/// - Everything that is not tied to one version uses [`Raw`]: the
35///   [`HandleDispatchFrom`](crate::HandleDispatchFrom) layer,
36///   [`MatchDispatch`](crate::util::MatchDispatch), builders whose version
37///   guard is disabled, and requests sent through [`ConnectionTo`].
38///
39/// The [`v1`](crate::v1) and `v2` modules name the typed handles:
40/// `v1::Responder<T>` is `Responder<T, V1>`, `v2::SentRequest<T>` is
41/// `SentRequest<T, V2>`, `v1::Builder<Host>` is `Builder<Host, .., V1>`, and so
42/// on.
43///
44/// Every handle also has `raw_id()`, which returns the wire ID as a
45/// [`RawJsonRpcId`] whatever the marker. A [`Raw`] handle converts to a typed
46/// one with `into_protocol`, and any handle converts back with `into_raw`.
47///
48/// ```
49/// use agent_client_protocol::{RawJsonRpcId, Responder, V1, v1::schema};
50///
51/// fn log_request(responder: &Responder<schema::PromptResponse, V1>) {
52///     let id: &schema::RequestId = responder.id();
53///     let wire_id: &RawJsonRpcId = responder.raw_id();
54///     assert_eq!(RawJsonRpcId::from(id.clone()), *wire_id);
55/// }
56///
57/// fn log_raw_request(responder: &Responder<schema::PromptResponse>) {
58///     // `Responder<T>` is `Responder<T, Raw>`.
59///     let id: &RawJsonRpcId = responder.id();
60///     # let _ = id;
61/// }
62/// ```
63///
64/// Generic code can accept handles of any version:
65///
66/// ```
67/// use agent_client_protocol::{Protocol, Responder, v1::schema};
68///
69/// fn reject<P: Protocol>(responder: Responder<schema::PromptResponse, P>) -> agent_client_protocol::Result<()> {
70///     responder.respond_with_error(agent_client_protocol::Error::invalid_params())
71/// }
72/// ```
73///
74/// This trait is sealed: the SDK defines one marker per supported protocol
75/// version, plus [`Raw`].
76#[allow(private_bounds)]
77pub trait Protocol: sealed::Sealed + Copy + Debug + Default + Send + Sync + 'static {
78    /// The request ID type of this protocol version.
79    type RequestId: Clone
80        + Debug
81        + Display
82        + Eq
83        + Hash
84        + Serialize
85        + Into<RawJsonRpcId>
86        + Send
87        + Sync
88        + 'static;
89
90    /// The connection type that builder callbacks receive for `Counterpart`.
91    ///
92    /// Every connection type converts back to the version-neutral
93    /// [`ConnectionTo`] with `Into`, so code generic over `P` can reach the
94    /// raw connection:
95    ///
96    /// ```
97    /// use agent_client_protocol::{Agent, ConnectionTo, Protocol};
98    ///
99    /// fn raw<P: Protocol>(connection: P::Connection<Agent>) -> ConnectionTo<Agent> {
100    ///     connection.into()
101    /// }
102    /// ```
103    type Connection<Counterpart: Role>: Clone
104        + Send
105        + Sync
106        + Into<ConnectionTo<Counterpart>>
107        + 'static;
108}
109
110pub(crate) mod sealed {
111    use super::{ConnectionTo, Protocol, RawJsonRpcId, Role};
112
113    pub trait Sealed {
114        /// The ACP protocol version this marker types, or `None` for
115        /// [`Raw`](super::Raw).
116        fn protocol_version() -> Option<crate::schema::ProtocolVersion>;
117
118        /// Wraps the version-neutral connection in this version's connection
119        /// type.
120        fn connection_from_raw<Counterpart: Role>(
121            connection: ConnectionTo<Counterpart>,
122        ) -> <Self as Protocol>::Connection<Counterpart>
123        where
124            Self: Protocol;
125
126        /// Converts a wire ID to this version's request ID. Fails only for
127        /// IDs the version does not allow.
128        fn request_id_from_raw(
129            id: &RawJsonRpcId,
130        ) -> Result<<Self as Protocol>::RequestId, crate::Error>
131        where
132            Self: Protocol;
133
134        /// The request ID for a string ID the SDK generated.
135        fn request_id_from_string(id: String) -> <Self as Protocol>::RequestId
136        where
137            Self: Protocol;
138    }
139}
140
141/// The ACP protocol version `P` types, or `None` for [`Raw`].
142pub(crate) fn protocol_version<P: Protocol>() -> Option<crate::schema::ProtocolVersion> {
143    <P as sealed::Sealed>::protocol_version()
144}
145
146pub(crate) fn connection_from_raw<P: Protocol, Counterpart: Role>(
147    connection: ConnectionTo<Counterpart>,
148) -> P::Connection<Counterpart> {
149    <P as sealed::Sealed>::connection_from_raw(connection)
150}
151
152/// The version-neutral protocol marker of the raw dispatch layer, and the
153/// default.
154///
155/// `id()` on a `Raw` handle returns the [`RawJsonRpcId`] exactly as it
156/// appears on the wire, which may be `null`. See [`Protocol`].
157#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
158pub struct Raw;
159
160impl sealed::Sealed for Raw {
161    fn protocol_version() -> Option<crate::schema::ProtocolVersion> {
162        None
163    }
164
165    fn connection_from_raw<Counterpart: Role>(
166        connection: ConnectionTo<Counterpart>,
167    ) -> ConnectionTo<Counterpart> {
168        connection
169    }
170
171    fn request_id_from_raw(id: &RawJsonRpcId) -> Result<RawJsonRpcId, crate::Error> {
172        Ok(id.clone())
173    }
174
175    fn request_id_from_string(id: String) -> RawJsonRpcId {
176        RawJsonRpcId::Str(id)
177    }
178}
179
180impl Protocol for Raw {
181    type RequestId = RawJsonRpcId;
182    type Connection<Counterpart: Role> = ConnectionTo<Counterpart>;
183}
184
185/// ACP protocol version 1.
186///
187/// See [`Protocol`].
188#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
189pub struct V1;
190
191impl sealed::Sealed for V1 {
192    fn protocol_version() -> Option<crate::schema::ProtocolVersion> {
193        Some(crate::schema::ProtocolVersion::V1)
194    }
195
196    fn connection_from_raw<Counterpart: Role>(
197        connection: ConnectionTo<Counterpart>,
198    ) -> super::v1_connection::ConnectionTo<Counterpart> {
199        super::v1_connection::ConnectionTo { inner: connection }
200    }
201
202    fn request_id_from_raw(id: &RawJsonRpcId) -> Result<v1::RequestId, crate::Error> {
203        Ok(id.clone().into())
204    }
205
206    fn request_id_from_string(id: String) -> v1::RequestId {
207        v1::RequestId::Str(id)
208    }
209}
210
211impl Protocol for V1 {
212    type RequestId = v1::RequestId;
213    type Connection<Counterpart: Role> = super::v1_connection::ConnectionTo<Counterpart>;
214}
215
216/// ACP protocol version 2.
217///
218/// See [`Protocol`]. Request IDs on v2 connections are never `null`: the SDK
219/// rejects such requests before dispatch, so `id()` on a `V2` handle is
220/// infallible.
221#[cfg(feature = "unstable_protocol_v2")]
222#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
223pub struct V2;
224
225#[cfg(feature = "unstable_protocol_v2")]
226impl sealed::Sealed for V2 {
227    fn protocol_version() -> Option<crate::schema::ProtocolVersion> {
228        Some(crate::schema::ProtocolVersion::V2)
229    }
230
231    fn connection_from_raw<Counterpart: Role>(
232        connection: ConnectionTo<Counterpart>,
233    ) -> super::v2_connection::ConnectionTo<Counterpart> {
234        super::v2_connection::ConnectionTo { inner: connection }
235    }
236
237    fn request_id_from_raw(id: &RawJsonRpcId) -> Result<v2::RequestId, crate::Error> {
238        id.clone().try_into()
239    }
240
241    fn request_id_from_string(id: String) -> v2::RequestId {
242        v2::RequestId::Str(id)
243    }
244}
245
246#[cfg(feature = "unstable_protocol_v2")]
247impl Protocol for V2 {
248    type RequestId = v2::RequestId;
249    type Connection<Counterpart: Role> = super::v2_connection::ConnectionTo<Counterpart>;
250}
251
252/// The ID of a request handle: the wire ID and the same ID as the handle's
253/// protocol version types it.
254pub(crate) struct HandleId<P: Protocol> {
255    raw: RawJsonRpcId,
256    typed: P::RequestId,
257}
258
259impl<P: Protocol> HandleId<P> {
260    /// The ID of a request the SDK sends.
261    pub(crate) fn generated(id: String) -> Self {
262        Self {
263            typed: P::request_id_from_string(id.clone()),
264            raw: RawJsonRpcId::Str(id),
265        }
266    }
267
268    /// The ID of a received request or response. Fails only for IDs `P` does
269    /// not allow.
270    pub(crate) fn from_raw(raw: RawJsonRpcId) -> Result<Self, crate::Error> {
271        Ok(Self {
272            typed: P::request_id_from_raw(&raw)?,
273            raw,
274        })
275    }
276
277    pub(crate) fn raw(&self) -> &RawJsonRpcId {
278        &self.raw
279    }
280
281    pub(crate) fn typed(&self) -> &P::RequestId {
282        &self.typed
283    }
284
285    /// The same ID for protocol `Q`. Fails only for IDs `Q` does not allow.
286    pub(crate) fn into_protocol<Q: Protocol>(self) -> Result<HandleId<Q>, crate::Error> {
287        HandleId::from_raw(self.raw)
288    }
289
290    /// The same ID in the raw dispatch layer.
291    pub(crate) fn into_raw(self) -> HandleId<Raw> {
292        HandleId::raw_layer(self.raw)
293    }
294}
295
296impl HandleId<Raw> {
297    /// The ID of a message in the raw dispatch layer, which allows every
298    /// JSON-RPC ID.
299    pub(crate) fn raw_layer(raw: RawJsonRpcId) -> Self {
300        Self {
301            typed: raw.clone(),
302            raw,
303        }
304    }
305}
306
307impl<P: Protocol> Clone for HandleId<P> {
308    fn clone(&self) -> Self {
309        Self {
310            raw: self.raw.clone(),
311            typed: self.typed.clone(),
312        }
313    }
314}
315
316impl<P: Protocol> Debug for HandleId<P> {
317    /// Formats as the wire ID, as the handles did before they were typed.
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        Debug::fmt(&self.raw, f)
320    }
321}