feat: revision-2025-03-26 without streamable http (#84)

1. Suppot revision 2025-03-26 data types
2. Support meta, progress tokne, and extensions in request/notification,
3. Remove `Message`, use `JsonRpcMessage` directly
This commit is contained in:
4t145 2025-04-07 09:47:39 +08:00 • committed by GitHub
commit 68375460df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -2,18 +2,24 @@ use std::{borrow::Cow, sync::Arc};
mod annotated;
mod capabilities;
mod content;
mod extension;
mod meta;
mod prompt;
mod resource;
mod serde_impl;
mod tool;
pub use annotated::*;
pub use capabilities::*;
pub use content::*;
pub use extension::*;
pub use meta::*;
pub use prompt::*;
pub use resource::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub use tool::*;
/// You can use [`crate::object!`] or [`crate::model::object`] to create a json object quickly.
pub type JsonObject<F = Value> = serde_json::Map<String, F>;
/// unwrap the JsonObject under [`serde_json::Value`]
@ -28,6 +34,7 @@ pub fn object(value: serde_json::Value) -> JsonObject {
}
}
/// Use this macro just like [`serde_json::json!`]
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! object {
@ -85,7 +92,7 @@ macro_rules! const_string {
const_string!(JsonRpcVersion2_0 = "2.0");
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd)]
pub struct ProtocolVersion(Cow<'static, str>);
impl Default for ProtocolVersion {
@ -94,8 +101,9 @@ impl Default for ProtocolVersion {
}
}
impl ProtocolVersion {
pub const LATEST: Self = Self(Cow::Borrowed("2024-11-05"));
pub const V_2024_11_05: Self = Self::LATEST;
pub const V_2025_03_26: Self = Self(Cow::Borrowed("2025-03-26"));
pub const V_2024_11_05: Self = Self(Cow::Borrowed("2024-11-05"));
pub const LATEST: Self = Self::V_2025_03_26;
}
impl Serialize for ProtocolVersion {
@ -116,17 +124,28 @@ impl<'de> Deserialize<'de> for ProtocolVersion {
#[allow(clippy::single_match)]
match s.as_str() {
"2024-11-05" => return Ok(ProtocolVersion::V_2024_11_05),
"2025-03-26" => return Ok(ProtocolVersion::V_2025_03_26),
_ => {}
}
Ok(ProtocolVersion(Cow::Owned(s)))
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum NumberOrString {
Number(u32),
String(Arc<str>),
}
impl NumberOrString {
pub fn into_json_value(self) -> Value {
match self {
NumberOrString::Number(n) => Value::Number(serde_json::Number::from(n)),
NumberOrString::String(s) => Value::String(s.to_string()),
}
}
}
impl std::fmt::Display for NumberOrString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -166,41 +185,58 @@ impl<'de> Deserialize<'de> for NumberOrString {
}
pub type RequestId = NumberOrString;
pub type ProgressToken = NumberOrString;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct WithMeta<P = JsonObject, M = ()> {
#[serde(skip_serializing_if = "Option::is_none")]
pub _meta: Option<M>,
#[serde(flatten)]
pub inner: P,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RequestMeta {
progress_token: ProgressToken,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Request<M = String, P = Option<WithMeta<JsonObject, RequestMeta>>> {
#[serde(transparent)]
pub struct ProgressToken(pub NumberOrString);
#[derive(Debug, Clone)]
pub struct Request<M = String, P = JsonObject> {
pub method: M,
// #[serde(skip_serializing_if = "Option::is_none")]
pub params: P,
/// extensions will carry anything possible in the context, including [`Meta`]
///
/// this is similar with the Extensions in `http` crate
pub extensions: Extensions,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub struct RequestOptionalParam<M = String, P = JsonObject> {
pub method: M,
// #[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<P>,
/// extensions will carry anything possible in the context, including [`Meta`]
///
/// this is similar with the Extensions in `http` crate
pub extensions: Extensions,
}
#[derive(Debug, Clone)]
pub struct RequestNoParam<M = String> {
pub method: M,
/// extensions will carry anything possible in the context, including [`Meta`]
///
/// this is similar with the Extensions in `http` crate
pub extensions: Extensions,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Notification<M = String, P = Option<WithMeta<JsonObject, JsonObject>>> {
#[derive(Debug, Clone)]
pub struct Notification<M = String, P = JsonObject> {
pub method: M,
pub params: P,
/// extensions will carry anything possible in the context, including [`Meta`]
///
/// this is similar with the Extensions in `http` crate
pub extensions: Extensions,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub struct NotificationNoParam<M = String> {
pub method: M,
/// extensions will carry anything possible in the context, including [`Meta`]
///
/// this is similar with the Extensions in `http` crate
pub extensions: Extensions,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
@ -210,9 +246,9 @@ pub struct JsonRpcRequest<R = Request> {
#[serde(flatten)]
pub request: R,
}
type DefaultResponse = WithMeta<JsonObject, JsonObject>;
type DefaultResponse = JsonObject;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct JsonRpcResponse<R = DefaultResponse> {
pub struct JsonRpcResponse<R = JsonObject> {
pub jsonrpc: JsonRpcVersion2_0,
pub id: RequestId,
pub result: R,
@ -295,95 +331,111 @@ impl ErrorData {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
pub enum JsonRpcMessage<Req = Request, Resp = DefaultResponse, Noti = Notification> {
pub enum JsonRpcBatchRequestItem<Req, Not> {
Request(JsonRpcRequest<Req>),
Response(JsonRpcResponse<Resp>),
Notification(JsonRpcNotification<Noti>),
Error(JsonRpcError),
Notification(JsonRpcNotification<Not>),
}
impl<Req, Resp, Noti> JsonRpcMessage<Req, Resp, Noti> {
pub fn into_message(self) -> Message<Req, Resp, Noti> {
impl<Req, Not> JsonRpcBatchRequestItem<Req, Not> {
pub fn into_non_batch_message<Resp>(self) -> JsonRpcMessage<Req, Resp, Not> {
match self {
JsonRpcMessage::Request(JsonRpcRequest { id, request, .. }) => {
Message::Request(request, id)
}
JsonRpcMessage::Response(JsonRpcResponse { id, result, .. }) => {
Message::Response(result, id)
}
JsonRpcMessage::Notification(JsonRpcNotification { notification, .. }) => {
Message::Notification(notification)
}
JsonRpcMessage::Error(JsonRpcError { id, error, .. }) => Message::Error(error, id),
JsonRpcBatchRequestItem::Request(r) => JsonRpcMessage::Request(r),
JsonRpcBatchRequestItem::Notification(n) => JsonRpcMessage::Notification(n),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Message<Req = Request, Resp = DefaultResponse, Noti = Notification> {
Request(Req, RequestId),
Response(Resp, RequestId),
Error(ErrorData, RequestId),
Notification(Noti),
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
pub enum JsonRpcBatchResponseItem<Resp> {
Response(JsonRpcResponse<Resp>),
Error(JsonRpcError),
}
impl<Req, Resp, Noti> Message<Req, Resp, Noti> {
pub fn into_notification(self) -> Option<Noti> {
impl<Resp> JsonRpcBatchResponseItem<Resp> {
pub fn into_non_batch_message<Req, Not>(self) -> JsonRpcMessage<Req, Resp, Not> {
match self {
Message::Notification(notification) => Some(notification),
JsonRpcBatchResponseItem::Response(r) => JsonRpcMessage::Response(r),
JsonRpcBatchResponseItem::Error(e) => JsonRpcMessage::Error(e),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
pub enum JsonRpcMessage<Req = Request, Resp = DefaultResponse, Noti = Notification> {
Request(JsonRpcRequest<Req>),
Response(JsonRpcResponse<Resp>),
Notification(JsonRpcNotification<Noti>),
BatchRequest(Vec<JsonRpcBatchRequestItem<Req, Noti>>),
BatchResponse(Vec<JsonRpcBatchResponseItem<Resp>>),
Error(JsonRpcError),
}
impl<Req, Resp, Not> JsonRpcMessage<Req, Resp, Not> {
#[inline]
pub const fn request(request: Req, id: RequestId) -> Self {
JsonRpcMessage::Request(JsonRpcRequest {
jsonrpc: JsonRpcVersion2_0,
id,
request,
})
}
#[inline]
pub const fn response(response: Resp, id: RequestId) -> Self {
JsonRpcMessage::Response(JsonRpcResponse {
jsonrpc: JsonRpcVersion2_0,
id,
result: response,
})
}
#[inline]
pub const fn error(error: ErrorData, id: RequestId) -> Self {
JsonRpcMessage::Error(JsonRpcError {
jsonrpc: JsonRpcVersion2_0,
id,
error,
})
}
#[inline]
pub const fn notification(notification: Not) -> Self {
JsonRpcMessage::Notification(JsonRpcNotification {
jsonrpc: JsonRpcVersion2_0,
notification,
})
}
pub fn into_request(self) -> Option<(Req, RequestId)> {
match self {
JsonRpcMessage::Request(r) => Some((r.request, r.id)),
_ => None,
}
}
pub fn into_response(self) -> Option<(Resp, RequestId)> {
match self {
Message::Response(result, id) => Some((result, id)),
JsonRpcMessage::Response(r) => Some((r.result, r.id)),
_ => None,
}
}
pub fn into_request(self) -> Option<(Req, RequestId)> {
pub fn into_notification(self) -> Option<Not> {
match self {
Message::Request(request, id) => Some((request, id)),
JsonRpcMessage::Notification(n) => Some(n.notification),
_ => None,
}
}
pub fn into_error(self) -> Option<(ErrorData, RequestId)> {
match self {
Message::Error(error, id) => Some((error, id)),
JsonRpcMessage::Error(e) => Some((e.error, e.id)),
_ => None,
}
}
pub fn into_result(self) -> Option<(Result<Resp, ErrorData>, RequestId)> {
match self {
Message::Response(result, id) => Some((Ok(result), id)),
Message::Error(error, id) => Some((Err(error), id)),
JsonRpcMessage::Response(r) => Some((Ok(r.result), r.id)),
JsonRpcMessage::Error(e) => Some((Err(e.error), e.id)),
_ => None,
}
}
pub fn into_json_rpc_message(self) -> JsonRpcMessage<Req, Resp, Noti> {
match self {
Message::Request(request, id) => JsonRpcMessage::Request(JsonRpcRequest {
jsonrpc: JsonRpcVersion2_0,
id,
request,
}),
Message::Response(result, id) => JsonRpcMessage::Response(JsonRpcResponse {
jsonrpc: JsonRpcVersion2_0,
id,
result,
}),
Message::Error(error, id) => JsonRpcMessage::Error(JsonRpcError {
jsonrpc: JsonRpcVersion2_0,
id,
error,
}),
Message::Notification(notification) => {
JsonRpcMessage::Notification(JsonRpcNotification {
jsonrpc: JsonRpcVersion2_0,
notification,
})
}
}
}
}
/// # Empty result
@ -493,12 +545,10 @@ impl Implementation {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct PaginatedRequestParamInner {
pub struct PaginatedRequestParam {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
pub type PaginatedRequestParam = Option<PaginatedRequestParamInner>;
const_string!(PingRequestMethod = "ping");
pub type PingRequest = RequestNoParam<PingRequestMethod>;
@ -512,6 +562,9 @@ pub struct ProgressNotificationParam {
/// Total number of items to process (or total progress required), if known
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<u32>,
/// An optional message describing the current progress.
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
pub type ProgressNotification = Notification<ProgressNotificationMethod, ProgressNotificationParam>;
@ -533,14 +586,15 @@ macro_rules! paginated_result {
}
const_string!(ListResourcesRequestMethod = "resources/list");
pub type ListResourcesRequest = Request<ListResourcesRequestMethod, PaginatedRequestParam>;
pub type ListResourcesRequest =
RequestOptionalParam<ListResourcesRequestMethod, PaginatedRequestParam>;
paginated_result!(ListResourcesResult {
resources: Vec<Resource>
});
const_string!(ListResourceTemplatesRequestMethod = "resources/templates/list");
pub type ListResourceTemplatesRequest =
Request<ListResourceTemplatesRequestMethod, PaginatedRequestParam>;
RequestOptionalParam<ListResourceTemplatesRequestMethod, PaginatedRequestParam>;
paginated_result!(ListResourceTemplatesResult {
resource_templates: Vec<ResourceTemplate>
});
@ -589,7 +643,7 @@ pub type ResourceUpdatedNotification =
Notification<ResourceUpdatedNotificationMethod, ResourceUpdatedNotificationParam>;
const_string!(ListPromptsRequestMethod = "prompts/list");
pub type ListPromptsRequest = Request<ListPromptsRequestMethod, PaginatedRequestParam>;
pub type ListPromptsRequest = RequestOptionalParam<ListPromptsRequestMethod, PaginatedRequestParam>;
paginated_result!(ListPromptsResult {
prompts: Vec<Prompt>
});
@ -793,7 +847,7 @@ impl CallToolResult {
}
const_string!(ListToolsRequestMethod = "tools/list");
pub type ListToolsRequest = Request<ListToolsRequestMethod, PaginatedRequestParam>;
pub type ListToolsRequest = RequestOptionalParam<ListToolsRequestMethod, PaginatedRequestParam>;
paginated_result!(
ListToolsResult {
tools: Vec<Tool>
@ -840,7 +894,7 @@ macro_rules! ts_union {
export type $U: ident =
$(|)?$($V: ident)|*;
) => {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum $U {
$($V($V),)*
@ -884,7 +938,6 @@ impl ClientResult {
}
pub type ClientJsonRpcMessage = JsonRpcMessage<ClientRequest, ClientResult, ClientNotification>;
pub type ClientMessage = Message<ClientRequest, ClientResult, ClientNotification>;
ts_union!(
export type ServerRequest =
@ -926,7 +979,6 @@ impl ServerResult {
}
pub type ServerJsonRpcMessage = JsonRpcMessage<ServerRequest, ServerResult, ServerNotification>;
pub type ServerMessage = Message<ServerRequest, ServerResult, ServerNotification>;
impl TryInto<CancelledNotification> for ServerNotification {
type Error = ServerNotification;
@ -975,12 +1027,14 @@ mod tests {
});
let message: ClientJsonRpcMessage =
serde_json::from_value(raw.clone()).expect("invalid notification");
let message = message.into_message();
match &message {
ClientMessage::Notification(ClientNotification::InitializedNotification(_n)) => {}
ClientJsonRpcMessage::Notification(JsonRpcNotification {
notification: ClientNotification::InitializedNotification(_n),
..
}) => {}
_ => panic!("Expected Notification"),
}
let json = serde_json::to_value(message.into_json_rpc_message()).expect("valid json");
let json = serde_json::to_value(message).expect("valid json");
assert_eq!(json, raw);
}
@ -999,7 +1053,7 @@ mod tests {
assert_eq!(r.id, RequestId::Number(1));
assert_eq!(r.request.method, "request");
assert_eq!(
&r.request.params.as_ref().unwrap().inner,
&r.request.params,
json!({"key": "value"})
.as_object()
.expect("should be an object")
@ -1057,10 +1111,7 @@ mod tests {
});
let request: ClientJsonRpcMessage =
serde_json::from_value(request.clone()).expect("invalid request");
let (request, id) = request
.into_message()
.into_request()
.expect("expect request");
let (request, id) = request.into_request().expect("should be a request");
assert_eq!(id, RequestId::Number(1));
match request {
ClientRequest::InitializeRequest(Request {
@ -1071,6 +1122,7 @@ mod tests {
capabilities,
client_info,
},
..
}) => {
assert_eq!(capabilities.roots.unwrap().list_changed, Some(true));
assert_eq!(capabilities.sampling.unwrap().len(), 0);
@ -1083,7 +1135,6 @@ mod tests {
serde_json::from_value(raw_response_json.clone()).expect("invalid response");
let (response, id) = server_response
.clone()
.into_message()
.into_response()
.expect("expect response");
assert_eq!(id, RequestId::Number(1));
@ -1113,4 +1164,11 @@ mod tests {
assert_eq!(server_response_json, raw_response_json);
}
#[test]
fn test_protocol_version_order() {
let v1 = ProtocolVersion::V_2024_11_05;
let v2 = ProtocolVersion::V_2025_03_26;
assert!(v1 < v2);
}
}