Skip to main content

agent_client_protocol/schema/
proxy_protocol.rs

1//! JSON-RPC support for the proxy-chain messages.
2//!
3//! The message types come from the schema crate, where they sit behind its
4//! `unstable_proxy_chains` feature, which this SDK always enables:
5//! [`v1::schema`](crate::v1::schema) (and, with `unstable_protocol_v2`,
6//! `v2::schema`) has
7//! [`InitializeProxyRequest`](crate::v1::schema::InitializeProxyRequest),
8//! [`InitializeProxyResponse`](crate::v1::schema::InitializeProxyResponse),
9//! [`ProxySuccessorRequest`](crate::v1::schema::ProxySuccessorRequest),
10//! [`ProxySuccessorNotification`](crate::v1::schema::ProxySuccessorNotification),
11//! [`ProxySuccessorResponse`](crate::v1::schema::ProxySuccessorResponse), the
12//! [`ProxyRequest`](crate::v1::schema::ProxyRequest),
13//! [`ProxyResponse`](crate::v1::schema::ProxyResponse) and
14//! [`ProxyNotification`](crate::v1::schema::ProxyNotification) unions, and the method
15//! names in [`PROXY_METHOD_NAMES`](crate::v1::schema::PROXY_METHOD_NAMES).
16//!
17//! This module implements the SDK's JSON-RPC traits for those types and adds
18//! [`SuccessorMessage`], a version-neutral typed view of a `proxy/successor`
19//! envelope.
20//!
21//! See the [Proxy Chains RFD](https://agentclientprotocol.com/rfds/proxy-chains).
22
23use std::sync::Arc;
24
25use serde_json::value::RawValue;
26
27use crate::{JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, UntypedMessage};
28use crate::{schema::IntoOption, v1::schema as v1};
29
30/// `proxy/successor`, the method of every successor envelope.
31const PROXY_SUCCESSOR: &str = v1::PROXY_METHOD_NAMES.proxy_successor;
32/// `proxy/initialize`, the method a conductor initializes proxies with.
33const PROXY_INITIALIZE: &str = v1::PROXY_METHOD_NAMES.proxy_initialize;
34
35// =============================================================================
36// Schema proxy types
37// =============================================================================
38
39/// Implement the SDK's JSON-RPC traits for one version's schema proxy types.
40macro_rules! impl_proxy_jsonrpc {
41    ($ns:ident) => {
42        const _: () = {
43            use $crate::$ns::schema::{
44                InitializeProxyRequest, InitializeProxyResponse, ProxyNotification, ProxyRequest,
45                ProxyResponse, ProxySuccessorNotification, ProxySuccessorRequest,
46                ProxySuccessorResponse,
47            };
48
49            impl_jsonrpc_request!(
50                InitializeProxyRequest,
51                InitializeProxyResponse,
52                "proxy/initialize"
53            );
54            impl_jsonrpc_notification!(ProxySuccessorNotification, "proxy/successor");
55
56            impl JsonRpcMessage for ProxySuccessorRequest {
57                fn matches_method(method: &str) -> bool {
58                    method == PROXY_SUCCESSOR
59                }
60
61                fn method(&self) -> &str {
62                    PROXY_SUCCESSOR
63                }
64
65                fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
66                    UntypedMessage::new(PROXY_SUCCESSOR, self)
67                }
68
69                fn parse_message(
70                    method: &str,
71                    params: &serde_json::Value,
72                ) -> Result<Self, crate::Error> {
73                    if method != PROXY_SUCCESSOR {
74                        return Err(crate::Error::method_not_found());
75                    }
76                    crate::util::json_cast_params(params)
77                }
78            }
79
80            // The response is the wrapped request's result, unchanged.
81            impl JsonRpcRequest for ProxySuccessorRequest {
82                type Response = ProxySuccessorResponse;
83            }
84
85            impl JsonRpcResponse for ProxySuccessorResponse {
86                fn into_json(self, _method: &str) -> Result<serde_json::Value, crate::Error> {
87                    serde_json::from_str(self.0.get()).map_err(crate::Error::into_internal_error)
88                }
89
90                fn from_json(
91                    _method: &str,
92                    value: serde_json::Value,
93                ) -> Result<Self, crate::Error> {
94                    Ok(Self::new(to_raw_value(&value)?))
95                }
96            }
97
98            impl JsonRpcMessage for ProxyRequest {
99                fn matches_method(method: &str) -> bool {
100                    method == PROXY_INITIALIZE || method == PROXY_SUCCESSOR
101                }
102
103                fn method(&self) -> &str {
104                    <ProxyRequest>::method(self)
105                }
106
107                fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
108                    UntypedMessage::new(<ProxyRequest>::method(self), self)
109                }
110
111                fn parse_message(
112                    method: &str,
113                    params: &serde_json::Value,
114                ) -> Result<Self, crate::Error> {
115                    match method {
116                        PROXY_INITIALIZE => crate::util::json_cast_params(params).map(
117                            |value: InitializeProxyRequest| {
118                                Self::InitializeProxyRequest(value.into())
119                            },
120                        ),
121                        PROXY_SUCCESSOR => crate::util::json_cast_params(params).map(
122                            |value: ProxySuccessorRequest| {
123                                Self::ProxySuccessorRequest(value.into())
124                            },
125                        ),
126                        _ => Err(crate::Error::method_not_found()),
127                    }
128                }
129            }
130
131            impl JsonRpcRequest for ProxyRequest {
132                type Response = ProxyResponse;
133            }
134
135            impl JsonRpcResponse for ProxyResponse {
136                fn into_json(self, _method: &str) -> Result<serde_json::Value, crate::Error> {
137                    serde_json::to_value(self).map_err(crate::Error::into_internal_error)
138                }
139
140                fn from_json(method: &str, value: serde_json::Value) -> Result<Self, crate::Error> {
141                    match method {
142                        PROXY_INITIALIZE => {
143                            crate::util::json_cast(value).map(|value: InitializeProxyResponse| {
144                                Self::InitializeProxyResponse(value.into())
145                            })
146                        }
147                        PROXY_SUCCESSOR => ProxySuccessorResponse::from_json(method, value).map(
148                            |value: ProxySuccessorResponse| {
149                                Self::ProxySuccessorResponse(value.into())
150                            },
151                        ),
152                        _ => Err(crate::Error::method_not_found()),
153                    }
154                }
155            }
156
157            impl JsonRpcMessage for ProxyNotification {
158                fn matches_method(method: &str) -> bool {
159                    method == PROXY_SUCCESSOR
160                }
161
162                fn method(&self) -> &str {
163                    <ProxyNotification>::method(self)
164                }
165
166                fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
167                    UntypedMessage::new(<ProxyNotification>::method(self), self)
168                }
169
170                fn parse_message(
171                    method: &str,
172                    params: &serde_json::Value,
173                ) -> Result<Self, crate::Error> {
174                    match method {
175                        PROXY_SUCCESSOR => crate::util::json_cast_params(params).map(
176                            |value: ProxySuccessorNotification| {
177                                Self::ProxySuccessorNotification(value.into())
178                            },
179                        ),
180                        _ => Err(crate::Error::method_not_found()),
181                    }
182                }
183            }
184
185            impl JsonRpcNotification for ProxyNotification {}
186        };
187    };
188}
189
190impl_proxy_jsonrpc!(v1);
191#[cfg(feature = "unstable_protocol_v2")]
192impl_proxy_jsonrpc!(v2);
193
194fn to_raw_value(value: &serde_json::Value) -> Result<Arc<RawValue>, crate::Error> {
195    serde_json::value::to_raw_value(value)
196        .map(Arc::from)
197        .map_err(crate::Error::into_internal_error)
198}
199
200// =============================================================================
201// Typed successor envelopes
202// =============================================================================
203
204/// A typed view of a `proxy/successor` envelope.
205///
206/// The schema's [`ProxySuccessorRequest`](crate::v1::schema::ProxySuccessorRequest)
207/// and [`ProxySuccessorNotification`](crate::v1::schema::ProxySuccessorNotification)
208/// carry the wrapped message as a raw method name and raw params, with one
209/// copy of each type per protocol version. `SuccessorMessage<M>` holds the
210/// wrapped message as an SDK message type `M` instead and works for every
211/// protocol version. For a wrapped request its response type is the wrapped
212/// request's response type, because the result of a wrapped request is
213/// returned unchanged as the result of `proxy/successor`.
214///
215/// Its JSON-RPC form is the schema envelope: the wrapped message's `method`
216/// and `params`, next to the envelope's optional `_meta`. The SDK's proxy
217/// routing uses it to wrap and unwrap messages for a proxy's successor.
218///
219/// Build one with [`SuccessorMessage::new`] and [`SuccessorMessage::meta`].
220#[derive(Debug, Clone)]
221#[non_exhaustive]
222pub struct SuccessorMessage<M: JsonRpcMessage = UntypedMessage> {
223    /// The message to be sent to the successor component.
224    pub message: M,
225
226    /// Metadata for the envelope itself, sent as `_meta`. The wrapped message
227    /// keeps its own `_meta` in its params. A received `_meta` that is not an
228    /// object is ignored, as the schema envelope specifies.
229    pub meta: Option<v1::Meta>,
230}
231
232impl<M: JsonRpcMessage> SuccessorMessage<M> {
233    /// Wrap `message` for the successor, without envelope metadata.
234    #[must_use]
235    pub fn new(message: M) -> Self {
236        Self {
237            message,
238            meta: None,
239        }
240    }
241
242    /// Set the envelope's `_meta` object, replacing any existing metadata.
243    #[must_use]
244    pub fn meta(mut self, meta: impl IntoOption<v1::Meta>) -> Self {
245        self.meta = meta.into_option();
246        self
247    }
248}
249
250impl<M: JsonRpcMessage> JsonRpcMessage for SuccessorMessage<M> {
251    fn matches_method(method: &str) -> bool {
252        method == PROXY_SUCCESSOR
253    }
254
255    fn method(&self) -> &str {
256        PROXY_SUCCESSOR
257    }
258
259    fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
260        let UntypedMessage { method, params } = self.message.to_untyped_message()?;
261        // The params are always sent, as `null` when the wrapped message has
262        // none. The schema treats `null` and an omitted `params` the same.
263        let envelope = v1::ProxySuccessorRequest::new(method)
264            .params(to_raw_value(&params)?)
265            .meta(self.meta.clone());
266        UntypedMessage::new(PROXY_SUCCESSOR, envelope)
267    }
268
269    fn parse_message(method: &str, params: &serde_json::Value) -> Result<Self, crate::Error> {
270        if method != PROXY_SUCCESSOR {
271            return Err(crate::Error::method_not_found());
272        }
273        // A notification envelope has the same fields as a request envelope.
274        let envelope: v1::ProxySuccessorNotification = crate::util::json_cast_params(params)?;
275        if !M::matches_method(&envelope.method) {
276            return Err(crate::Error::method_not_found());
277        }
278        // Take the wrapped params from the JSON already in hand rather than
279        // re-parsing the envelope's raw copy.
280        let inner_params = params
281            .get("params")
282            .cloned()
283            .unwrap_or(serde_json::Value::Null);
284        let message = M::parse_message(&envelope.method, &inner_params)?;
285        Ok(SuccessorMessage::new(message).meta(envelope.meta))
286    }
287}
288
289impl<Req: JsonRpcRequest> JsonRpcRequest for SuccessorMessage<Req> {
290    type Response = Req::Response;
291}
292
293impl<Notif: JsonRpcNotification> JsonRpcNotification for SuccessorMessage<Notif> {}