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;