Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ jobs:
- name: Lint
run: |
cargo clippy
cargo clippy -p agent-client-protocol-schema --all-targets --no-default-features --features unstable,unstable_protocol_v2,tracing
cargo clippy --all-targets --all-features

- name: Build
Expand Down
9 changes: 7 additions & 2 deletions agent-client-protocol-schema/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ rustdoc-args = ["--cfg", "docsrs"]
workspace = true

[features]
default = ["schemars"]
# Implement `schemars::JsonSchema` for protocol types. This remains enabled by
# default to preserve the existing default API. Size-sensitive consumers can
# omit the dependency and those impls with `default-features = false`.
schemars = ["dep:schemars", "serde_with/schemars_1"]
unstable = [
"unstable_auth_methods",
"unstable_llm_providers",
Expand Down Expand Up @@ -53,12 +58,12 @@ tracing = ["dep:tracing"]
[dependencies]
anyhow = "1"
derive_more = { version = "2", features = ["from", "display"] }
schemars = { version = "1" }
schemars = { version = "1", optional = true }
# `rc` is required for Arc-based protocol strings and RawValue payloads.
serde = { version = "1", features = ["derive", "rc"] }
# `raw_value` powers extension passthrough payloads; `preserve_order` keeps
# caller-supplied object key order in arbitrary JSON values and maps.
serde_json = { version = "1", features = ["preserve_order", "raw_value"] }
serde_with = { version = "3.20.0", features = ["json", "schemars_1"] }
serde_with = { version = "3.20.0", features = ["json"] }
strum = { version = "0.28", features = ["derive"] }
tracing = { version = "0.1", default-features = false, optional = true }
22 changes: 20 additions & 2 deletions agent-client-protocol-schema/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
//! (programs that use generative AI to autonomously modify code).
//!
//! This crate is **only** the schema: the request, response, and
//! notification types, plus serde plumbing and JSON Schema generation. For
//! the runtime pieces (transport, connection setup, the `Agent` / `Client`
//! notification types, plus serde plumbing and optional JSON Schema generation.
//! For the runtime pieces (transport, connection setup, the `Agent` / `Client`
//! traits, etc.) use the higher-level [`agent-client-protocol`] crate, which
//! builds on top of these types.
//!
Expand All @@ -29,6 +29,13 @@
//! [`v1::AgentNotification`], and the matching client-side trio used by SDK
//! crates to dispatch incoming JSON-RPC messages.
//!
//! ## Cargo features
//!
//! The `schemars` feature implements `schemars::JsonSchema` for the protocol
//! types. It is enabled by default to preserve the existing default API.
//! Consumers that only need serialization can disable default features to omit
//! the dependency and those trait implementations.
//!
//! ## Versioning
//!
//! Stable protocol types are exposed through explicit version modules. For
Expand All @@ -51,8 +58,19 @@ pub use version::*;

#[cfg(test)]
mod serde_json_feature_tests {
#[cfg(feature = "schemars")]
use schemars::JsonSchema;
use serde_json::Value;

#[cfg(feature = "schemars")]
#[test]
fn protocol_types_implement_json_schema_when_enabled() {
fn assert_json_schema<T: JsonSchema>() {}

assert_json_schema::<crate::ProtocolVersion>();
assert_json_schema::<crate::v1::InitializeRequest>();
}

#[test]
fn serde_json_values_preserve_object_key_order() {
let Value::Object(object) =
Expand Down
49 changes: 23 additions & 26 deletions agent-client-protocol-schema/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use std::sync::Arc;

use derive_more::{Display, From};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;

Expand All @@ -19,19 +18,9 @@ use serde_with::skip_serializing_none;
/// \[1\] The use of Null as a value for the id member in a Request object is discouraged, because this specification uses a value of Null for Responses with an unknown id. Also, because JSON-RPC 1.0 uses an id value of Null for Notifications this could cause confusion in handling.
///
/// \[2\] Fractional parts may be problematic, since many decimal fractions cannot be represented exactly as binary fractions.
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(
Debug,
PartialEq,
Clone,
Hash,
Eq,
Deserialize,
Serialize,
PartialOrd,
Ord,
Display,
JsonSchema,
From,
Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize, PartialOrd, Ord, Display, From,
)]
#[serde(untagged)]
#[allow(
Expand All @@ -50,12 +39,13 @@ pub enum RequestId {
}

/// A JSON-RPC request object.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[allow(
clippy::exhaustive_structs,
reason = "This comes from the JSON-RPC specification itself"
)]
#[schemars(rename = "{Params}", extend("x-docs-ignore" = true))]
#[cfg_attr(feature = "schemars", schemars(rename = "{Params}", extend("x-docs-ignore" = true)))]
#[skip_serializing_none]
pub struct Request<Params> {
/// The request id used to correlate the matching response.
Expand All @@ -67,13 +57,14 @@ pub struct Request<Params> {
}

/// A JSON-RPC response object.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[allow(
clippy::exhaustive_enums,
reason = "This comes from the JSON-RPC specification itself"
)]
#[serde(untagged)]
#[schemars(rename = "{Result}", extend("x-docs-ignore" = true))]
#[cfg_attr(feature = "schemars", schemars(rename = "{Result}", extend("x-docs-ignore" = true)))]
pub enum Response<Result, Error> {
/// A successful JSON-RPC response.
Result {
Expand Down Expand Up @@ -109,12 +100,13 @@ impl<R, E> Response<R, E> {
}

/// A JSON-RPC notification object.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[allow(
clippy::exhaustive_structs,
reason = "This comes from the JSON-RPC specification itself"
)]
#[schemars(rename = "{Params}", extend("x-docs-ignore" = true))]
#[cfg_attr(feature = "schemars", schemars(rename = "{Params}", extend("x-docs-ignore" = true)))]
#[skip_serializing_none]
pub struct Notification<Params> {
/// The notification method name.
Expand All @@ -123,8 +115,9 @@ pub struct Notification<Params> {
pub params: Option<Params>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[schemars(inline)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", schemars(inline))]
enum JsonRpcVersion {
#[serde(rename = "2.0")]
V2,
Expand All @@ -134,8 +127,9 @@ enum JsonRpcVersion {
/// [required by JSON-RPC 2.0 Specification][1].
///
/// [1]: https://www.jsonrpc.org/specification#compatibility
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[schemars(inline)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", schemars(inline))]
pub struct JsonRpcMessage<M> {
jsonrpc: JsonRpcVersion,
#[serde(flatten)]
Expand Down Expand Up @@ -174,14 +168,17 @@ pub struct EmptyJsonRpcBatch;
impl std::error::Error for EmptyJsonRpcBatch {}

/// A non-empty JSON-RPC 2.0 batch message.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
#[schemars(inline)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schemars", schemars(inline))]
#[serde(transparent)]
#[allow(
clippy::exhaustive_structs,
reason = "This comes from the JSON-RPC specification itself"
)]
pub struct JsonRpcBatch<M>(#[schemars(length(min = 1))] Vec<JsonRpcMessage<M>>);
pub struct JsonRpcBatch<M>(
#[cfg_attr(feature = "schemars", schemars(length(min = 1)))] Vec<JsonRpcMessage<M>>,
);

impl<M> JsonRpcBatch<M> {
/// Creates a non-empty JSON-RPC batch.
Expand Down
6 changes: 3 additions & 3 deletions agent-client-protocol-schema/src/serde_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ use std::{
sync::Arc,
};

use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{DeserializeAs, de::DeserializeAsWrap};

Expand Down Expand Up @@ -307,8 +306,9 @@ impl IntoOption<serde_json::Value> for Cow<'_, str> {
/// a: MaybeUndefined<i32>,
/// }
/// ```
#[derive(Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, JsonSchema)]
#[schemars(with = "Option<Option<T>>", inline)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[cfg_attr(feature = "schemars", schemars(with = "Option<Option<T>>", inline))]
#[expect(clippy::exhaustive_enums)]
pub enum MaybeUndefined<T> {
/// The field was not present.
Expand Down
Loading