Skip to content

Commit 1405ed1

Browse files
authored
jsonrpc: Specify mandatory jsonrpc Version Property (#52)
* Wrap outgoing `rpc::OutgoingMessage` into `rpc::JsonRpcMessage` * Wrap when sending errors too
1 parent 1b5e08d commit 1405ed1

File tree

2 files changed

+56
-23
lines changed

2 files changed

+56
-23
lines changed

rust/rpc.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ where
158158
message = outgoing_rx.next() => {
159159
if let Some(message) = message {
160160
outgoing_line.clear();
161-
serde_json::to_writer(&mut outgoing_line, &message).map_err(Error::into_internal_error)?;
161+
serde_json::to_writer(&mut outgoing_line, &JsonRpcMessage::wrap(&message)).map_err(Error::into_internal_error)?;
162162
log::trace!("send: {}", String::from_utf8_lossy(&outgoing_line));
163163
outgoing_line.push(b'\n');
164164
outgoing_bytes.write_all(&outgoing_line).await.ok();
@@ -190,7 +190,7 @@ where
190190
result: ResponseResult::Error(err),
191191
};
192192

193-
serde_json::to_writer(&mut outgoing_line, &error_response)?;
193+
serde_json::to_writer(&mut outgoing_line, &JsonRpcMessage::wrap(&error_response))?;
194194
log::trace!("send: {}", String::from_utf8_lossy(&outgoing_line));
195195
outgoing_line.push(b'\n');
196196
outgoing_bytes.write_all(&outgoing_line).await.ok();
@@ -325,6 +325,34 @@ pub enum OutgoingMessage<Local: Side, Remote: Side> {
325325
},
326326
}
327327

328+
/// Either [`OutgoingMessage`] or [`IncomingMessage`] with `"jsonrpc": "2.0"` specified as
329+
/// [required by JSON-RPC 2.0 Specification][1].
330+
///
331+
/// [1]: https://www.jsonrpc.org/specification#compatibility
332+
#[derive(Debug, Serialize, Deserialize)]
333+
pub struct JsonRpcMessage<M> {
334+
jsonrpc: &'static str,
335+
#[serde(flatten)]
336+
message: M,
337+
}
338+
339+
impl<M> JsonRpcMessage<M> {
340+
/// Used version of [JSON-RPC protocol].
341+
///
342+
/// [JSON-RPC]: https://www.jsonrpc.org
343+
pub const VERSION: &'static str = "2.0";
344+
345+
/// Wraps the provided [`OutgoingMessage`] or [`IncomingMessage`] into a versioned
346+
/// [`JsonRpcMessage`].
347+
#[must_use]
348+
pub fn wrap(message: M) -> Self {
349+
Self {
350+
jsonrpc: Self::VERSION,
351+
message,
352+
}
353+
}
354+
}
355+
328356
#[derive(Debug, Serialize, Deserialize, Clone)]
329357
#[serde(rename_all = "snake_case")]
330358
pub enum ResponseResult<Res> {

rust/rpc_tests.rs

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -612,49 +612,54 @@ async fn test_full_conversation_flow() {
612612
async fn test_notification_wire_format() {
613613
use crate::{
614614
AgentNotification, AgentSide, CancelNotification, ClientNotification, ClientSide,
615-
ContentBlock, SessionNotification, SessionUpdate, TextContent, rpc::OutgoingMessage,
615+
ContentBlock, SessionNotification, SessionUpdate, TextContent,
616+
rpc::{JsonRpcMessage, OutgoingMessage},
616617
};
617618
use serde_json::{Value, json};
618619

619620
// Test client -> agent notification wire format
620-
let outgoing_msg = OutgoingMessage::<ClientSide, AgentSide>::Notification {
621-
method: "cancel",
622-
params: Some(ClientNotification::CancelNotification(CancelNotification {
623-
session_id: SessionId("test-123".into()),
624-
})),
625-
};
621+
let outgoing_msg =
622+
JsonRpcMessage::wrap(OutgoingMessage::<ClientSide, AgentSide>::Notification {
623+
method: "cancel",
624+
params: Some(ClientNotification::CancelNotification(CancelNotification {
625+
session_id: SessionId("test-123".into()),
626+
})),
627+
});
626628

627629
let serialized: Value = serde_json::to_value(&outgoing_msg).unwrap();
628630
assert_eq!(
629631
serialized,
630632
json!({
633+
"jsonrpc": "2.0",
631634
"method": "cancel",
632635
"params": {
633636
"sessionId": "test-123"
634-
}
637+
},
635638
})
636639
);
637640

638641
// Test agent -> client notification wire format
639-
let outgoing_msg = OutgoingMessage::<AgentSide, ClientSide>::Notification {
640-
method: "sessionUpdate",
641-
params: Some(AgentNotification::SessionNotification(
642-
SessionNotification {
643-
session_id: SessionId("test-456".into()),
644-
update: SessionUpdate::AgentMessageChunk {
645-
content: ContentBlock::Text(TextContent {
646-
annotations: None,
647-
text: "Hello".to_string(),
648-
}),
642+
let outgoing_msg =
643+
JsonRpcMessage::wrap(OutgoingMessage::<AgentSide, ClientSide>::Notification {
644+
method: "sessionUpdate",
645+
params: Some(AgentNotification::SessionNotification(
646+
SessionNotification {
647+
session_id: SessionId("test-456".into()),
648+
update: SessionUpdate::AgentMessageChunk {
649+
content: ContentBlock::Text(TextContent {
650+
annotations: None,
651+
text: "Hello".to_string(),
652+
}),
653+
},
649654
},
650-
},
651-
)),
652-
};
655+
)),
656+
});
653657

654658
let serialized: Value = serde_json::to_value(&outgoing_msg).unwrap();
655659
assert_eq!(
656660
serialized,
657661
json!({
662+
"jsonrpc": "2.0",
658663
"method": "sessionUpdate",
659664
"params": {
660665
"sessionId": "test-456",

0 commit comments

Comments
 (0)