agent_client_protocol_schema/
rpc.rs1use std::sync::Arc;
2
3use derive_more::{Display, From};
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use serde_with::skip_serializing_none;
7
8#[derive(
18 Debug,
19 PartialEq,
20 Clone,
21 Hash,
22 Eq,
23 Deserialize,
24 Serialize,
25 PartialOrd,
26 Ord,
27 Display,
28 JsonSchema,
29 From,
30)]
31#[serde(untagged)]
32#[allow(
33 clippy::exhaustive_enums,
34 reason = "This comes from the JSON-RPC specification itself"
35)]
36#[from(String, i64)]
37pub enum RequestId {
38 #[display("null")]
39 Null,
40 Number(i64),
41 Str(String),
42}
43
44#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
45#[allow(
46 clippy::exhaustive_structs,
47 reason = "This comes from the JSON-RPC specification itself"
48)]
49#[schemars(rename = "{Params}", extend("x-docs-ignore" = true))]
50#[skip_serializing_none]
51pub struct Request<Params> {
52 pub id: RequestId,
53 pub method: Arc<str>,
54 pub params: Option<Params>,
55}
56
57#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
58#[allow(
59 clippy::exhaustive_enums,
60 reason = "This comes from the JSON-RPC specification itself"
61)]
62#[serde(untagged)]
63#[schemars(rename = "{Result}", extend("x-docs-ignore" = true))]
64pub enum Response<Result, Error> {
65 Result { id: RequestId, result: Result },
66 Error { id: RequestId, error: Error },
67}
68
69impl<R, E> Response<R, E> {
70 #[must_use]
71 pub fn new(id: impl Into<RequestId>, result: std::result::Result<R, E>) -> Self {
72 match result {
73 Ok(result) => Self::Result {
74 id: id.into(),
75 result,
76 },
77 Err(error) => Self::Error {
78 id: id.into(),
79 error,
80 },
81 }
82 }
83}
84
85#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
86#[allow(
87 clippy::exhaustive_structs,
88 reason = "This comes from the JSON-RPC specification itself"
89)]
90#[schemars(rename = "{Params}", extend("x-docs-ignore" = true))]
91#[skip_serializing_none]
92pub struct Notification<Params> {
93 pub method: Arc<str>,
94 pub params: Option<Params>,
95}
96
97#[derive(Debug, Serialize, Deserialize, JsonSchema)]
98#[schemars(inline)]
99enum JsonRpcVersion {
100 #[serde(rename = "2.0")]
101 V2,
102}
103
104#[derive(Debug, Serialize, Deserialize, JsonSchema)]
109#[schemars(inline)]
110pub struct JsonRpcMessage<M> {
111 jsonrpc: JsonRpcVersion,
112 #[serde(flatten)]
113 message: M,
114}
115
116impl<M> JsonRpcMessage<M> {
117 #[must_use]
119 pub fn wrap(message: M) -> Self {
120 Self {
121 jsonrpc: JsonRpcVersion::V2,
122 message,
123 }
124 }
125
126 #[must_use]
128 pub fn into_inner(self) -> M {
129 self.message
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 use crate::{
138 AgentNotification, CancelNotification, ClientNotification, ContentBlock, ContentChunk,
139 SessionId, SessionNotification, SessionUpdate, TextContent,
140 };
141 use serde_json::{Number, Value, json};
142
143 #[test]
144 fn id_deserialization() {
145 let id = serde_json::from_value::<RequestId>(Value::Null).unwrap();
146 assert_eq!(id, RequestId::Null);
147
148 let id = serde_json::from_value::<RequestId>(Value::Number(Number::from_u128(1).unwrap()))
149 .unwrap();
150 assert_eq!(id, RequestId::Number(1));
151
152 let id = serde_json::from_value::<RequestId>(Value::Number(Number::from_i128(-1).unwrap()))
153 .unwrap();
154 assert_eq!(id, RequestId::Number(-1));
155
156 let id = serde_json::from_value::<RequestId>(Value::String("id".to_owned())).unwrap();
157 assert_eq!(id, RequestId::Str("id".to_owned()));
158 }
159
160 #[test]
161 fn id_serialization() {
162 let id = serde_json::to_value(RequestId::Null).unwrap();
163 assert_eq!(id, Value::Null);
164
165 let id = serde_json::to_value(RequestId::Number(1)).unwrap();
166 assert_eq!(id, Value::Number(Number::from_u128(1).unwrap()));
167
168 let id = serde_json::to_value(RequestId::Number(-1)).unwrap();
169 assert_eq!(id, Value::Number(Number::from_i128(-1).unwrap()));
170
171 let id = serde_json::to_value(RequestId::Str("id".to_owned())).unwrap();
172 assert_eq!(id, Value::String("id".to_owned()));
173 }
174
175 #[test]
176 fn id_display() {
177 let id = RequestId::Null;
178 assert_eq!(id.to_string(), "null");
179
180 let id = RequestId::Number(1);
181 assert_eq!(id.to_string(), "1");
182
183 let id = RequestId::Number(-1);
184 assert_eq!(id.to_string(), "-1");
185
186 let id = RequestId::Str("id".to_owned());
187 assert_eq!(id.to_string(), "id");
188 }
189
190 #[test]
191 fn notification_wire_format() {
192 let outgoing_msg = JsonRpcMessage::wrap(Notification {
194 method: "cancel".into(),
195 params: Some(ClientNotification::CancelNotification(CancelNotification {
196 session_id: SessionId("test-123".into()),
197 meta: None,
198 })),
199 });
200
201 let serialized: Value = serde_json::to_value(&outgoing_msg).unwrap();
202 assert_eq!(
203 serialized,
204 json!({
205 "jsonrpc": "2.0",
206 "method": "cancel",
207 "params": {
208 "sessionId": "test-123"
209 },
210 })
211 );
212
213 let outgoing_msg = JsonRpcMessage::wrap(Notification {
215 method: "sessionUpdate".into(),
216 params: Some(AgentNotification::SessionNotification(
217 SessionNotification {
218 session_id: SessionId("test-456".into()),
219 update: SessionUpdate::AgentMessageChunk(ContentChunk {
220 content: ContentBlock::Text(TextContent {
221 annotations: None,
222 text: "Hello".to_string(),
223 meta: None,
224 }),
225 #[cfg(feature = "unstable_message_id")]
226 message_id: None,
227 meta: None,
228 }),
229 meta: None,
230 },
231 )),
232 });
233
234 let serialized: Value = serde_json::to_value(&outgoing_msg).unwrap();
235 assert_eq!(
236 serialized,
237 json!({
238 "jsonrpc": "2.0",
239 "method": "sessionUpdate",
240 "params": {
241 "sessionId": "test-456",
242 "update": {
243 "sessionUpdate": "agent_message_chunk",
244 "content": {
245 "type": "text",
246 "text": "Hello"
247 }
248 }
249 }
250 })
251 );
252 }
253}