Skip to main content

agent_client_protocol/schema/
mod.rs

1//! Version-neutral ACP schema items.
2//!
3//! Each protocol version's messages (requests, responses, notifications and
4//! supporting types) are in that version's SDK module: [`crate::v1::schema`]
5//! and, with `unstable_protocol_v2`, `agent_client_protocol::v2::schema`. This
6//! module holds only the items every version shares: [`ProtocolVersion`],
7//! [`MaybeUndefined`], the [`IntoOption`] and [`IntoMaybeUndefined`]
8//! conversion traits, and [`SuccessorMessage`].
9//!
10//! The types come from the `agent-client-protocol-schema` crate, which this
11//! crate pins to one exact version. Use them through the SDK instead of
12//! depending on `agent-client-protocol-schema` directly.
13
14// ---------------------------------------------------------------------------
15// Macros for implementing JsonRpc traits on schema types
16// ---------------------------------------------------------------------------
17
18/// Implement `JsonRpcMessage`, `JsonRpcRequest`, and `JsonRpcResponse` for a
19/// request/response pair from the schema crate.
20///
21/// ```ignore
22/// impl_jsonrpc_request!(PromptRequest, PromptResponse, "session/prompt");
23/// ```
24macro_rules! impl_jsonrpc_request {
25    ($req:ty, $resp:ty, $method:literal) => {
26        impl $crate::JsonRpcMessage for $req {
27            fn matches_method(method: &str) -> bool {
28                method == $method
29            }
30
31            fn method(&self) -> &str {
32                $method
33            }
34
35            fn to_untyped_message(&self) -> Result<$crate::UntypedMessage, $crate::Error> {
36                $crate::UntypedMessage::new($method, self)
37            }
38
39            fn parse_message(
40                method: &str,
41                params: &serde_json::Value,
42            ) -> Result<Self, $crate::Error> {
43                if method != $method {
44                    return Err($crate::Error::method_not_found());
45                }
46                $crate::util::json_cast_params(params)
47            }
48        }
49
50        impl $crate::JsonRpcRequest for $req {
51            type Response = $resp;
52        }
53
54        impl $crate::JsonRpcResponse for $resp {
55            fn into_json(self, _method: &str) -> Result<serde_json::Value, $crate::Error> {
56                serde_json::to_value(self).map_err($crate::Error::into_internal_error)
57            }
58
59            fn from_json(_method: &str, value: serde_json::Value) -> Result<Self, $crate::Error> {
60                $crate::util::json_cast(&value)
61            }
62        }
63    };
64}
65
66/// Implement `JsonRpcMessage` and `JsonRpcNotification` for a notification type
67/// from the schema crate.
68///
69/// ```ignore
70/// impl_jsonrpc_notification!(CancelNotification, "session/cancel");
71/// ```
72macro_rules! impl_jsonrpc_notification {
73    ($notif:ty, $method:literal) => {
74        impl $crate::JsonRpcMessage for $notif {
75            fn matches_method(method: &str) -> bool {
76                method == $method
77            }
78
79            fn method(&self) -> &str {
80                $method
81            }
82
83            fn to_untyped_message(&self) -> Result<$crate::UntypedMessage, $crate::Error> {
84                $crate::UntypedMessage::new($method, self)
85            }
86
87            fn parse_message(
88                method: &str,
89                params: &serde_json::Value,
90            ) -> Result<Self, $crate::Error> {
91                if method != $method {
92                    return Err($crate::Error::method_not_found());
93                }
94                $crate::util::json_cast_params(params)
95            }
96        }
97
98        impl $crate::JsonRpcNotification for $notif {}
99    };
100}
101
102/// Implement `JsonRpcMessage` and `JsonRpcRequest` for an enum that dispatches
103/// across multiple request types, with an extension method fallback.
104///
105/// Variants can optionally have `#[cfg(...)]` attributes for conditional compilation.
106///
107/// ```ignore
108/// impl_jsonrpc_request_enum!(ClientRequest {
109///     InitializeRequest => "initialize",
110///     PromptRequest => "session/prompt",
111///     [ext] ExtMethodRequest,
112/// });
113/// ```
114macro_rules! impl_jsonrpc_request_enum {
115    ($enum:ty {
116        $( $(#[$meta:meta])* $variant:ident => $method:literal, )*
117        [ext] $ext_variant:ident,
118    }) => {
119        impl $crate::JsonRpcMessage for $enum {
120            fn matches_method(_method: &str) -> bool {
121                true
122            }
123
124            fn method(&self) -> &str {
125                // The schema enum's inherent `method()` (path syntax prefers
126                // inherent items) covers every variant, including ones this
127                // macro does not list.
128                <$enum>::method(self)
129            }
130
131            fn to_untyped_message(&self) -> Result<$crate::UntypedMessage, $crate::Error> {
132                $crate::UntypedMessage::new(self.method(), self)
133            }
134
135            fn parse_message(
136                method: &str,
137                params: &serde_json::Value,
138            ) -> Result<Self, $crate::Error> {
139                match method {
140                    $( $(#[$meta])* $method => $crate::util::json_cast_params(params).map(Self::$variant), )*
141                    _ => {
142                        if method.starts_with('_') {
143                            $crate::util::json_cast_params(params).map(
144                                |ext_req: $crate::v1::schema::ExtRequest| {
145                                    Self::$ext_variant($crate::v1::schema::ExtRequest::new(
146                                        method.to_string(),
147                                        ext_req.params,
148                                    ))
149                                },
150                            )
151                        } else {
152                            Err($crate::Error::method_not_found())
153                        }
154                    }
155                }
156            }
157        }
158
159        impl $crate::JsonRpcRequest for $enum {
160            type Response = serde_json::Value;
161        }
162    };
163}
164
165/// Implement `JsonRpcMessage` and `JsonRpcNotification` for an enum that
166/// dispatches across multiple notification types, with an extension fallback.
167///
168/// Variants can optionally have `#[cfg(...)]` attributes for conditional compilation.
169///
170/// ```ignore
171/// impl_jsonrpc_notification_enum!(AgentNotification {
172///     SessionNotification => "session/update",
173///     [ext] ExtNotification,
174/// });
175/// ```
176macro_rules! impl_jsonrpc_notification_enum {
177    ($enum:ty {
178        $( $(#[$meta:meta])* $variant:ident => $method:literal, )*
179        [ext] $ext_variant:ident,
180    }) => {
181        impl $crate::JsonRpcMessage for $enum {
182            fn matches_method(_method: &str) -> bool {
183                true
184            }
185
186            fn method(&self) -> &str {
187                // The schema enum's inherent `method()` (path syntax prefers
188                // inherent items) covers every variant, including ones this
189                // macro does not list.
190                <$enum>::method(self)
191            }
192
193            fn to_untyped_message(&self) -> Result<$crate::UntypedMessage, $crate::Error> {
194                $crate::UntypedMessage::new(self.method(), self)
195            }
196
197            fn parse_message(
198                method: &str,
199                params: &serde_json::Value,
200            ) -> Result<Self, $crate::Error> {
201                match method {
202                    $( $(#[$meta])* $method => $crate::util::json_cast_params(params).map(Self::$variant), )*
203                    _ => {
204                        if method.starts_with('_') {
205                            $crate::util::json_cast_params(params).map(
206                                |ext_notif: $crate::v1::schema::ExtNotification| {
207                                    Self::$ext_variant($crate::v1::schema::ExtNotification::new(
208                                        method.to_string(),
209                                        ext_notif.params,
210                                    ))
211                                },
212                            )
213                        } else {
214                            Err($crate::Error::method_not_found())
215                        }
216                    }
217                }
218            }
219        }
220
221        impl $crate::JsonRpcNotification for $enum {}
222    };
223}
224
225/// Implement `JsonRpcMessage` and `JsonRpcNotification` for a protocol-level
226/// notification enum (`$/`-prefixed methods), shared between the v1 and v2
227/// schema namespaces.
228///
229/// The incoming side (`matches_method`, `parse_message`) only recognizes the
230/// methods listed in the macro invocation: when the schema crate adds a
231/// protocol-level notification, list it here to parse it. The outgoing side
232/// (`method`, `to_untyped_message`) instead delegates to the schema enum's
233/// inherent `method()` and untagged serialization, which cover every variant,
234/// so unlisted variants still serialize with the correct method name.
235///
236/// ```ignore
237/// impl_jsonrpc_protocol_level_notification_enum!(ProtocolLevelNotification {
238///     CancelRequestNotification => "$/cancel_request",
239/// });
240/// ```
241macro_rules! impl_jsonrpc_protocol_level_notification_enum {
242    ($enum:ty {
243        $( $variant:ident => $method:literal, )*
244    }) => {
245        impl $crate::JsonRpcMessage for $enum {
246            fn matches_method(method: &str) -> bool {
247                matches!(method, $( $method )|*)
248            }
249
250            fn method(&self) -> &str {
251                // Resolves to the schema enum's *inherent* `method()` (path
252                // syntax prefers inherent items over trait items), which
253                // matches its variants exhaustively: the enum is only
254                // non-exhaustive downstream.
255                <$enum>::method(self)
256            }
257
258            fn to_untyped_message(&self) -> Result<$crate::UntypedMessage, $crate::Error> {
259                // The schema enum is `#[serde(untagged)]`, so serializing the
260                // enum serializes the inner notification.
261                $crate::UntypedMessage::new(<$enum>::method(self), self)
262            }
263
264            fn parse_message(
265                method: &str,
266                params: &serde_json::Value,
267            ) -> Result<Self, $crate::Error> {
268                match method {
269                    $( $method => $crate::util::json_cast_params(params).map(Self::$variant), )*
270                    _ => Err($crate::Error::method_not_found()),
271                }
272            }
273        }
274
275        impl $crate::JsonRpcNotification for $enum {}
276    };
277}
278
279/// Implement `JsonRpcResponse` for an enum that dispatches across multiple
280/// response types, with an extension method fallback.
281macro_rules! impl_jsonrpc_response_enum {
282    ($enum:ty {
283        $( $(#[$meta:meta])* $variant:ident => $method:literal, )*
284        [ext] $ext_variant:ident,
285    }) => {
286        impl $crate::JsonRpcResponse for $enum {
287            fn into_json(
288                self,
289                _method: &str,
290            ) -> Result<serde_json::Value, $crate::Error> {
291                serde_json::to_value(self).map_err($crate::Error::into_internal_error)
292            }
293
294            fn from_json(
295                method: &str,
296                value: serde_json::Value,
297            ) -> Result<Self, $crate::Error> {
298                match method {
299                    $( $(#[$meta])* $method => $crate::util::json_cast(value).map(Self::$variant), )*
300                    _ => {
301                        if method.starts_with('_') {
302                            $crate::util::json_cast(value).map(Self::$ext_variant)
303                        } else {
304                            Err($crate::Error::method_not_found())
305                        }
306                    }
307                }
308            }
309        }
310    };
311}
312
313// Internal organization
314mod agent_to_client;
315mod client_to_agent;
316mod enum_impls;
317#[cfg(feature = "unstable_mcp_over_acp")]
318mod mcp;
319mod protocol_level;
320mod proxy_protocol;
321#[cfg(feature = "unstable_protocol_v2")]
322mod v2_impls;
323
324pub use agent_client_protocol_schema::{
325    IntoMaybeUndefined, IntoOption, MaybeUndefined, ProtocolVersion,
326};
327
328pub use proxy_protocol::SuccessorMessage;