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