From 924a1730c3d2834fe42d0b5ac47c771bb49bb914 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 17 Apr 2026 16:33:09 +0200 Subject: [PATCH 1/4] feat(rust-only): better tolerate malformed optional fields in deserialization Use `serde_with` helpers to default invalid optional values and skip bad vector entries across protocol types. Sometimes agents or clients send something not quite right. If it is an optional field, we can fallback to best-effort and retain as much information as possible without losing everything. --- Cargo.lock | 436 ++++++++++++++++++++++++++++++++- Cargo.toml | 1 + clippy.toml | 5 + docs/protocol/draft/schema.mdx | 3 + docs/protocol/schema.mdx | 3 + schema/schema.json | 1 + schema/schema.unstable.json | 1 + src/agent.rs | 93 +++++-- src/client.rs | 18 +- src/content.rs | 25 +- src/elicitation.rs | 4 + src/nes.rs | 107 +++++--- src/plan.rs | 3 + src/rpc.rs | 2 +- src/tool_call.rs | 17 +- 15 files changed, 654 insertions(+), 65 deletions(-) create mode 100644 clippy.toml diff --git a/Cargo.lock b/Cargo.lock index 524c4c07..891c586c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,18 +8,74 @@ version = "0.11.7" dependencies = [ "anyhow", "derive_more", - "schemars", + "schemars 1.2.1", "serde", "serde_json", + "serde_with", "strum", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -29,6 +85,56 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -58,24 +164,156 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -123,6 +361,24 @@ dependencies = [ "semver", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars" version = "1.2.1" @@ -208,6 +464,49 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_with" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.28.0" @@ -240,6 +539,37 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -258,6 +588,110 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index 7635971e..0f31c612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ derive_more = { version = "2", features = ["from", "display"] } schemars = { version = "1" } serde = { version = "1", features = ["derive", "rc"] } serde_json = { version = "1", features = ["raw_value"] } +serde_with = { version = "3.18.0", features = ["json", "schemars_1"] } strum = { version = "0.28", features = ["derive"] } [lints.rust] diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..60eef8c1 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,5 @@ +allowed-duplicate-crates = [ + "hashbrown", + "indexmap", + "schemars", +] diff --git a/docs/protocol/draft/schema.mdx b/docs/protocol/draft/schema.mdx index 76f259a7..19ce83da 100644 --- a/docs/protocol/draft/schema.mdx +++ b/docs/protocol/draft/schema.mdx @@ -2919,6 +2919,9 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/exte AvailableCommandInput | null} > Input for the command if required + + - Default: `null` + Command name (e.g., `create_plan`, `research_codebase`). diff --git a/docs/protocol/schema.mdx b/docs/protocol/schema.mdx index 79b6693e..7acf32d1 100644 --- a/docs/protocol/schema.mdx +++ b/docs/protocol/schema.mdx @@ -1305,6 +1305,9 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/exte AvailableCommandInput | null} > Input for the command if required + + - Default: `null` + Command name (e.g., `create_plan`, `research_codebase`). diff --git a/schema/schema.json b/schema/schema.json index 9aca01f4..97864bcf 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -442,6 +442,7 @@ "type": "null" } ], + "default": null, "description": "Input for the command if required" }, "name": { diff --git a/schema/schema.unstable.json b/schema/schema.unstable.json index abb6fb8a..874fd816 100644 --- a/schema/schema.unstable.json +++ b/schema/schema.unstable.json @@ -789,6 +789,7 @@ "type": "null" } ], + "default": null, "description": "Input for the command if required" }, "name": { diff --git a/src/agent.rs b/src/agent.rs index 0dbfb838..a0ded113 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -11,6 +11,7 @@ use std::collections::HashMap; use derive_more::{Display, From}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_with::{DefaultOnError, VecSkipError, serde_as}; #[cfg(feature = "unstable_llm_providers")] use crate::RequiredNullable; @@ -42,6 +43,7 @@ use crate::nes::{ /// Sent by the client to establish connection and negotiate capabilities. /// /// See protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization) +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = INITIALIZE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -55,7 +57,8 @@ pub struct InitializeRequest { /// Information about the Client name and version sent to the Agent. /// /// Note: in future versions of the protocol, this will be required. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub client_info: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -108,6 +111,7 @@ impl InitializeRequest { /// Contains the negotiated protocol version and agent capabilities. /// /// See protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization) +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = INITIALIZE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -122,12 +126,14 @@ pub struct InitializeResponse { #[serde(default)] pub agent_capabilities: AgentCapabilities, /// Authentication methods supported by the agent. + #[serde_as(deserialize_as = "VecSkipError<_>")] #[serde(default)] pub auth_methods: Vec, /// Information about the Agent name and version sent to the Client. /// /// Note: in future versions of the protocol, this will be required. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub agent_info: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -407,6 +413,7 @@ impl LogoutResponse { /// /// Authentication-related capabilities supported by the agent. #[cfg(feature = "unstable_logout")] +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -414,7 +421,8 @@ pub struct AgentAuthCapabilities { /// Whether the agent supports the logout method. /// /// By supplying `{}` it means that the agent supports the logout method. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub logout: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -980,6 +988,7 @@ impl NewSessionRequest { /// Response from creating a new session. /// /// See protocol docs: [Creating a Session](https://agentclientprotocol.com/protocol/session-setup#creating-a-session) +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_NEW_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -992,7 +1001,8 @@ pub struct NewSessionResponse { /// Initial mode state if supported by the Agent /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub modes: Option, /// **UNSTABLE** /// @@ -1000,10 +1010,12 @@ pub struct NewSessionResponse { /// /// Initial model state if supported by the Agent #[cfg(feature = "unstable_session_model")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -1154,6 +1166,7 @@ impl LoadSessionRequest { } /// Response from loading an existing session. +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_LOAD_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1162,6 +1175,7 @@ pub struct LoadSessionResponse { /// Initial mode state if supported by the Agent /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) + #[serde_as(deserialize_as = "DefaultOnError")] #[serde(default, skip_serializing_if = "Option::is_none")] pub modes: Option, /// **UNSTABLE** @@ -1170,9 +1184,11 @@ pub struct LoadSessionResponse { /// /// Initial model state if supported by the Agent #[cfg(feature = "unstable_session_model")] + #[serde_as(deserialize_as = "DefaultOnError")] #[serde(default, skip_serializing_if = "Option::is_none")] pub models: Option, /// Initial session configuration options if supported by the Agent. + #[serde_as(deserialize_as = "Option>")] #[serde(default, skip_serializing_if = "Option::is_none")] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1330,6 +1346,7 @@ impl ForkSessionRequest { /// /// Response from forking an existing session. #[cfg(feature = "unstable_session_fork")] +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_FORK_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1340,7 +1357,8 @@ pub struct ForkSessionResponse { /// Initial mode state if supported by the Agent /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub modes: Option, /// **UNSTABLE** /// @@ -1348,10 +1366,12 @@ pub struct ForkSessionResponse { /// /// Initial model state if supported by the Agent #[cfg(feature = "unstable_session_model")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -1516,6 +1536,7 @@ impl ResumeSessionRequest { /// /// Response from resuming an existing session. #[cfg(feature = "unstable_session_resume")] +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_RESUME_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1524,6 +1545,7 @@ pub struct ResumeSessionResponse { /// Initial mode state if supported by the Agent /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) + #[serde_as(deserialize_as = "DefaultOnError")] #[serde(default, skip_serializing_if = "Option::is_none")] pub modes: Option, /// **UNSTABLE** @@ -1532,9 +1554,11 @@ pub struct ResumeSessionResponse { /// /// Initial model state if supported by the Agent #[cfg(feature = "unstable_session_model")] + #[serde_as(deserialize_as = "DefaultOnError")] #[serde(default, skip_serializing_if = "Option::is_none")] pub models: Option, /// Initial session configuration options if supported by the Agent. + #[serde_as(deserialize_as = "Option>")] #[serde(default, skip_serializing_if = "Option::is_none")] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1768,12 +1792,14 @@ impl ListSessionsRequest { } /// Response from listing sessions. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_LIST_METHOD_NAME))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ListSessionsResponse { /// Array of session information objects + #[serde_as(deserialize_as = "VecSkipError<_>")] pub sessions: Vec, /// Opaque cursor token. If present, pass this in the next request's cursor parameter /// to fetch the next page. If absent, there are no more results. @@ -1817,6 +1843,7 @@ impl ListSessionsResponse { } /// Information about a session returned by session/list +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1837,10 +1864,12 @@ pub struct SessionInfo { pub additional_directories: Vec, /// Human-readable title for the session - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub title: Option, /// ISO 8601 timestamp of last activity - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub updated_at: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -1906,6 +1935,7 @@ impl SessionInfo { // Session modes /// The set of modes and the one currently active. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1913,6 +1943,7 @@ pub struct SessionModeState { /// The current mode the Agent is in. pub current_mode_id: SessionModeId, /// The set of modes that the Agent can operate in + #[serde_as(deserialize_as = "VecSkipError<_>")] pub available_modes: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -2332,6 +2363,7 @@ pub enum SessionConfigKind { } /// A session configuration option selector and its current state. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -2344,6 +2376,7 @@ pub struct SessionConfigOption { #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, /// Optional semantic category for this option (UX only). + #[serde_as(deserialize_as = "DefaultOnError")] #[serde(default, skip_serializing_if = "Option::is_none")] pub category: Option, /// Type-specific fields for this configuration option. @@ -2596,12 +2629,14 @@ impl SetSessionConfigOptionRequest { } /// Response to `session/set_config_option` method. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_CONFIG_OPTION_METHOD_NAME))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct SetSessionConfigOptionResponse { /// The full set of configuration options and their current values. + #[serde_as(deserialize_as = "VecSkipError<_>")] pub config_options: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -2992,6 +3027,7 @@ impl PromptRequest { /// Response from processing a user prompt. /// /// See protocol docs: [Check for Completion](https://agentclientprotocol.com/protocol/prompt-turn#4-check-for-completion) +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_PROMPT_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -3017,7 +3053,8 @@ pub struct PromptResponse { /// /// Token usage for this turn (optional). #[cfg(feature = "unstable_session_usage")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub usage: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -3179,6 +3216,7 @@ impl Usage { /// /// The set of models and the one currently active. #[cfg(feature = "unstable_session_model")] +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -3186,6 +3224,7 @@ pub struct SessionModelState { /// The current model the Agent is in. pub current_model_id: ModelId, /// The set of models that the Agent can use + #[serde_as(deserialize_as = "VecSkipError<_>")] pub available_models: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -3449,6 +3488,7 @@ impl ProviderCurrentConfig { /// /// Information about a configurable LLM provider. #[cfg(feature = "unstable_llm_providers")] +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -3456,6 +3496,7 @@ pub struct ProviderInfo { /// Provider identifier, for example "main" or "openai". pub id: String, /// Supported protocol types for this provider. + #[serde_as(deserialize_as = "VecSkipError<_>")] pub supported: Vec, /// Whether this provider is mandatory and cannot be disabled via `providers/disable`. /// If true, clients must not call `providers/disable` for this id. @@ -3547,12 +3588,14 @@ impl ListProvidersRequest { /// /// Response to `providers/list`. #[cfg(feature = "unstable_llm_providers")] +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_LIST_METHOD_NAME))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ListProvidersResponse { /// Configurable providers with current routing info suitable for UI display. + #[serde_as(deserialize_as = "VecSkipError<_>")] pub providers: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -3780,6 +3823,7 @@ impl DisableProvidersResponse { /// available features and content types. /// /// See protocol docs: [Agent Capabilities](https://agentclientprotocol.com/protocol/initialization#agent-capabilities) +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -3811,7 +3855,8 @@ pub struct AgentCapabilities { /// /// By supplying `{}` it means that the agent supports provider configuration methods. #[cfg(feature = "unstable_llm_providers")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub providers: Option, /// **UNSTABLE** /// @@ -3819,7 +3864,8 @@ pub struct AgentCapabilities { /// /// NES (Next Edit Suggestions) capabilities supported by the agent. #[cfg(feature = "unstable_nes")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub nes: Option, /// **UNSTABLE** /// @@ -3827,7 +3873,8 @@ pub struct AgentCapabilities { /// /// The position encoding selected by the agent from the client's supported encodings. #[cfg(feature = "unstable_nes")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub position_encoding: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -3981,12 +4028,14 @@ impl ProvidersCapabilities { /// Note: `session/load` is still handled by the top-level `load_session` capability. This will be unified in future versions of the protocol. /// /// See protocol docs: [Session Capabilities](https://agentclientprotocol.com/protocol/initialization#session-capabilities) +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct SessionCapabilities { /// Whether the agent supports `session/list`. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub list: Option, /// **UNSTABLE** /// @@ -3994,7 +4043,8 @@ pub struct SessionCapabilities { /// /// Whether the agent supports `additionalDirectories` on supported session lifecycle requests and `session/list`. #[cfg(feature = "unstable_session_additional_directories")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub additional_directories: Option, /// **UNSTABLE** /// @@ -4002,7 +4052,8 @@ pub struct SessionCapabilities { /// /// Whether the agent supports `session/fork`. #[cfg(feature = "unstable_session_fork")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub fork: Option, /// **UNSTABLE** /// @@ -4010,7 +4061,8 @@ pub struct SessionCapabilities { /// /// Whether the agent supports `session/resume`. #[cfg(feature = "unstable_session_resume")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub resume: Option, /// **UNSTABLE** /// @@ -4018,7 +4070,8 @@ pub struct SessionCapabilities { /// /// Whether the agent supports `session/close`. #[cfg(feature = "unstable_session_close")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub close: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at diff --git a/src/client.rs b/src/client.rs index 652cf4e5..8692efa3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -8,6 +8,7 @@ use std::{path::PathBuf, sync::Arc}; use derive_more::{Display, From}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_with::{DefaultOnError, VecSkipError, serde_as}; #[cfg(feature = "unstable_elicitation")] use crate::elicitation::{ @@ -152,11 +153,13 @@ impl CurrentModeUpdate { } /// Session configuration options have been updated. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ConfigOptionUpdate { /// The full set of configuration options and their current values. + #[serde_as(deserialize_as = "VecSkipError<_>")] pub config_options: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -249,6 +252,7 @@ impl SessionInfoUpdate { /// /// Context window and cost update for a session. #[cfg(feature = "unstable_session_usage")] +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -258,7 +262,8 @@ pub struct UsageUpdate { /// Total context window size in tokens. pub size: u64, /// Cumulative session cost (optional). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub cost: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -395,11 +400,13 @@ impl ContentChunk { } /// Available commands are ready or have changed +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct AvailableCommandsUpdate { /// Commands the agent can execute + #[serde_as(deserialize_as = "VecSkipError<_>")] pub available_commands: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -432,6 +439,7 @@ impl AvailableCommandsUpdate { } /// Information about a command. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -441,6 +449,8 @@ pub struct AvailableCommand { /// Human-readable description of what the command does. pub description: String, /// Input for the command if required + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub input: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -1473,6 +1483,7 @@ impl TerminalExitStatus { /// available features and methods. /// /// See protocol docs: [Client Capabilities](https://agentclientprotocol.com/protocol/initialization#client-capabilities) +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1501,6 +1512,7 @@ pub struct ClientCapabilities { /// Elicitation capabilities supported by the client. /// Determines which elicitation modes the agent may use. #[cfg(feature = "unstable_elicitation")] + #[serde_as(deserialize_as = "DefaultOnError")] #[serde(default, skip_serializing_if = "Option::is_none")] pub elicitation: Option, /// **UNSTABLE** @@ -1509,7 +1521,8 @@ pub struct ClientCapabilities { /// /// NES (Next Edit Suggestions) capabilities supported by the client. #[cfg(feature = "unstable_nes")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub nes: Option, /// **UNSTABLE** /// @@ -1517,6 +1530,7 @@ pub struct ClientCapabilities { /// /// The position encodings supported by the client, in order of preference. #[cfg(feature = "unstable_nes")] + #[serde_as(deserialize_as = "VecSkipError<_>")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub position_encodings: Vec, diff --git a/src/content.rs b/src/content.rs index c25539f6..a1ab6559 100644 --- a/src/content.rs +++ b/src/content.rs @@ -11,6 +11,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_with::{DefaultOnError, VecSkipError, serde_as}; use crate::{IntoOption, Meta}; @@ -59,10 +60,12 @@ pub enum ContentBlock { } /// Text provided to or from an LLM. +#[serde_as] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[non_exhaustive] pub struct TextContent { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub annotations: Option, pub text: String, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -109,11 +112,13 @@ impl> From for ContentBlock { } /// An image provided to or from an LLM. +#[serde_as] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ImageContent { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub annotations: Option, pub data: String, pub mime_type: String, @@ -165,11 +170,13 @@ impl ImageContent { } /// Audio provided to or from an LLM. +#[serde_as] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct AudioContent { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub annotations: Option, pub data: String, pub mime_type: String, @@ -212,10 +219,12 @@ impl AudioContent { } /// The contents of a resource, embedded into a prompt or tool call result. +#[serde_as] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[non_exhaustive] pub struct EmbeddedResource { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub annotations: Option, pub resource: EmbeddedResourceResource, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -359,11 +368,13 @@ impl BlobResourceContents { } /// A resource that the server is capable of reading, included in a prompt or tool call result. +#[serde_as] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ResourceLink { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub annotations: Option, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, @@ -442,11 +453,13 @@ impl ResourceLink { } /// Optional annotations for the client. The client can use annotations to inform how objects are used or displayed +#[serde_as] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, Default)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Annotations { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub audience: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub last_modified: Option, diff --git a/src/elicitation.rs b/src/elicitation.rs index 61dc6c7a..249cb0f0 100644 --- a/src/elicitation.rs +++ b/src/elicitation.rs @@ -10,6 +10,7 @@ use std::{collections::BTreeMap, sync::Arc}; use derive_more::{Display, From}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_with::{DefaultOnError, serde_as}; use crate::client::{ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME}; use crate::tool_call::ToolCallId; @@ -740,14 +741,17 @@ impl ElicitationSchema { /// This capability is not part of the spec yet, and may be removed or changed at any point. /// /// Elicitation capabilities supported by the client. +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ElicitationCapabilities { /// Whether the client supports form-based elicitation. + #[serde_as(deserialize_as = "DefaultOnError")] #[serde(default, skip_serializing_if = "Option::is_none")] pub form: Option, /// Whether the client supports URL-based elicitation. + #[serde_as(deserialize_as = "DefaultOnError")] #[serde(default, skip_serializing_if = "Option::is_none")] pub url: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional diff --git a/src/nes.rs b/src/nes.rs index 58323d22..26b851c9 100644 --- a/src/nes.rs +++ b/src/nes.rs @@ -6,6 +6,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_with::{DefaultOnError, VecSkipError, serde_as}; use crate::{IntoOption, Meta, SessionId}; @@ -92,15 +93,18 @@ impl Range { // Agent NES capabilities /// NES capabilities advertised by the agent during initialization. +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesCapabilities { /// Events the agent wants to receive. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub events: Option, /// Context the agent wants attached to each suggestion request. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub context: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -142,12 +146,14 @@ impl NesCapabilities { } /// Event capabilities the agent can consume. +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesEventCapabilities { /// Document event capabilities. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub document: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -183,24 +189,30 @@ impl NesEventCapabilities { } /// Document event capabilities the agent wants to receive. +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesDocumentEventCapabilities { /// Whether the agent wants `document/didOpen` events. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub did_open: Option, /// Whether the agent wants `document/didChange` events, and the sync kind. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub did_change: Option, /// Whether the agent wants `document/didClose` events. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub did_close: Option, /// Whether the agent wants `document/didSave` events. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub did_save: Option, /// Whether the agent wants `document/didFocus` events. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub did_focus: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -402,27 +414,34 @@ impl NesDocumentDidFocusCapabilities { } /// Context capabilities the agent wants attached to each suggestion request. +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesContextCapabilities { /// Whether the agent wants recent files context. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub recent_files: Option, /// Whether the agent wants related snippets context. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub related_snippets: Option, /// Whether the agent wants edit history context. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub edit_history: Option, /// Whether the agent wants user actions context. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub user_actions: Option, /// Whether the agent wants open files context. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub open_files: Option, /// Whether the agent wants diagnostics context. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub diagnostics: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -637,18 +656,22 @@ impl NesDiagnosticsCapabilities { // Client NES capabilities /// NES capabilities advertised by the client during initialization. +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ClientNesCapabilities { /// Whether the client supports the `jump` suggestion kind. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub jump: Option, /// Whether the client supports the `rename` suggestion kind. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub rename: Option, /// Whether the client supports the `searchAndReplace` suggestion kind. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub search_and_replace: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -1045,6 +1068,7 @@ impl DidFocusDocumentNotification { // NES session start /// Request to start an NES session. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_START_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1054,10 +1078,12 @@ pub struct StartNesRequest { #[serde(skip_serializing_if = "Option::is_none")] pub workspace_uri: Option, /// The workspace folders. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub workspace_folders: Option>, /// Repository metadata, if the workspace is a git repository. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub repository: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -1299,6 +1325,7 @@ pub enum NesTriggerKind { } /// Request for a code suggestion. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_SUGGEST_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1313,12 +1340,14 @@ pub struct SuggestNesRequest { /// The current cursor position. pub position: Position, /// The current text selection range, if any. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub selection: Option, /// What triggered this suggestion request. pub trigger_kind: NesTriggerKind, /// Context for the suggestion, included based on agent capabilities. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub context: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -1375,27 +1404,34 @@ impl SuggestNesRequest { } /// Context attached to a suggestion request. +#[serde_as] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesSuggestContext { /// Recently accessed files. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub recent_files: Option>, /// Related code snippets. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub related_snippets: Option>, /// Recent edit history. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub edit_history: Option>, /// Recent user actions (typing, navigation, etc.). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub user_actions: Option>, /// Currently open files in the editor. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub open_files: Option>, /// Current diagnostics (errors, warnings). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub diagnostics: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -1590,6 +1626,7 @@ impl NesUserAction { } /// An open file in the editor. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1599,10 +1636,12 @@ pub struct NesOpenFile { /// The language identifier. pub language_id: String, /// The visible range in the editor, if any. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub visible_range: Option, /// Timestamp in milliseconds since epoch of when the file was last focused. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub last_focused_ms: Option, } @@ -1683,12 +1722,14 @@ pub enum NesDiagnosticSeverity { // NES suggest response /// Response to `nes/suggest`. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_SUGGEST_METHOD_NAME))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct SuggestNesResponse { /// The list of suggestions. + #[serde_as(deserialize_as = "VecSkipError<_>")] pub suggestions: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -1737,6 +1778,7 @@ pub enum NesSuggestion { } /// A text edit suggestion. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1748,7 +1790,8 @@ pub struct NesEditSuggestion { /// The text edits to apply. pub edits: Vec, /// Optional suggested cursor position after applying edits. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub cursor_position: Option, } @@ -1933,6 +1976,7 @@ impl AcceptNesNotification { } /// Notification sent when a suggestion is rejected. +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_REJECT_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1943,7 +1987,8 @@ pub struct RejectNesNotification { /// The ID of the rejected suggestion. pub id: String, /// The reason for rejection. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub reason: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at diff --git a/src/plan.rs b/src/plan.rs index 60d75083..21cc0ac4 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -7,6 +7,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_with::{VecSkipError, serde_as}; use crate::{IntoOption, Meta}; @@ -17,6 +18,7 @@ use crate::{IntoOption, Meta}; /// Plans can evolve during execution as the agent discovers new requirements or completes tasks. /// /// See protocol docs: [Agent Plan](https://agentclientprotocol.com/protocol/agent-plan) +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -25,6 +27,7 @@ pub struct Plan { /// /// When updating a plan, the agent must send a complete list of all entries /// with their current status. The client replaces the entire plan with each update. + #[serde_as(deserialize_as = "VecSkipError<_>")] pub entries: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at diff --git a/src/rpc.rs b/src/rpc.rs index ada204de..48c0e984 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -500,7 +500,7 @@ mod tests { #[test] fn decode_rejects_empty_ext_method_name() { - let raw = serde_json::value::RawValue::from_string(r#"{}"#.to_string()).unwrap(); + let raw = serde_json::value::RawValue::from_string(r"{}".to_string()).unwrap(); let err = ClientSide::decode_request("_", Some(&raw)).unwrap_err(); assert_eq!(err.code, ErrorCode::MethodNotFound); diff --git a/src/tool_call.rs b/src/tool_call.rs index 50b736a5..4b96c986 100644 --- a/src/tool_call.rs +++ b/src/tool_call.rs @@ -9,6 +9,7 @@ use std::{path::PathBuf, sync::Arc}; use derive_more::{Display, From}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_with::{DefaultOnError, VecSkipError, serde_as}; use crate::{ContentBlock, Error, IntoOption, Meta, TerminalId}; @@ -18,6 +19,7 @@ use crate::{ContentBlock, Error, IntoOption, Meta, TerminalId}; /// such as reading files, executing code, or fetching data from external sources. /// /// See protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/tool-calls) +#[serde_as] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -34,10 +36,12 @@ pub struct ToolCall { #[serde(default, skip_serializing_if = "ToolCallStatus::is_default")] pub status: ToolCallStatus, /// Content produced by the tool call. + #[serde_as(deserialize_as = "VecSkipError<_>")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub content: Vec, /// File locations affected by this tool call. /// Enables "follow-along" features in clients. + #[serde_as(deserialize_as = "VecSkipError<_>")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub locations: Vec, /// Raw input parameters sent to the tool. @@ -205,24 +209,29 @@ impl ToolCallUpdate { /// Collections (content, locations) are overwritten, not extended. /// /// See protocol docs: [Updating](https://agentclientprotocol.com/protocol/tool-calls#updating) +#[serde_as] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ToolCallUpdateFields { /// Update the tool kind. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub kind: Option, /// Update the execution status. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub status: Option, /// Update the human-readable title. #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, /// Replace the content collection. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub content: Option>, /// Replace the locations collection. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "Option>")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub locations: Option>, /// Update the raw input. #[serde(skip_serializing_if = "Option::is_none")] From aa60197d1b83626516f094d978ea529b998fe86c Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 17 Apr 2026 16:49:32 +0200 Subject: [PATCH 2/4] Apply `skip_serializing_none` across protocol structs so optional fields are omitted consistently. --- docs/protocol/draft/schema.mdx | 3 - docs/protocol/schema.mdx | 3 - schema/schema.json | 1 - schema/schema.unstable.json | 1 - src/agent.rs | 270 ++++++++++++++++++++------------- src/client.rs | 106 ++++++++----- src/content.rs | 47 +++--- src/elicitation.rs | 67 ++++---- src/error.rs | 3 +- src/nes.rs | 173 ++++++++++++--------- src/plan.rs | 8 +- src/protocol_level.rs | 4 +- src/rpc.rs | 5 +- src/tool_call.rs | 36 ++--- 14 files changed, 410 insertions(+), 317 deletions(-) diff --git a/docs/protocol/draft/schema.mdx b/docs/protocol/draft/schema.mdx index 19ce83da..76f259a7 100644 --- a/docs/protocol/draft/schema.mdx +++ b/docs/protocol/draft/schema.mdx @@ -2919,9 +2919,6 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/exte AvailableCommandInput | null} > Input for the command if required - - - Default: `null` - Command name (e.g., `create_plan`, `research_codebase`). diff --git a/docs/protocol/schema.mdx b/docs/protocol/schema.mdx index 7acf32d1..79b6693e 100644 --- a/docs/protocol/schema.mdx +++ b/docs/protocol/schema.mdx @@ -1305,9 +1305,6 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/exte AvailableCommandInput | null} > Input for the command if required - - - Default: `null` - Command name (e.g., `create_plan`, `research_codebase`). diff --git a/schema/schema.json b/schema/schema.json index 97864bcf..9aca01f4 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -442,7 +442,6 @@ "type": "null" } ], - "default": null, "description": "Input for the command if required" }, "name": { diff --git a/schema/schema.unstable.json b/schema/schema.unstable.json index 874fd816..abb6fb8a 100644 --- a/schema/schema.unstable.json +++ b/schema/schema.unstable.json @@ -789,7 +789,6 @@ "type": "null" } ], - "default": null, "description": "Input for the command if required" }, "name": { diff --git a/src/agent.rs b/src/agent.rs index a0ded113..f212be46 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use derive_more::{Display, From}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::{DefaultOnError, VecSkipError, serde_as}; +use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; #[cfg(feature = "unstable_llm_providers")] use crate::RequiredNullable; @@ -44,6 +44,7 @@ use crate::nes::{ /// /// See protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization) #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = INITIALIZE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -58,14 +59,14 @@ pub struct InitializeRequest { /// /// Note: in future versions of the protocol, this will be required. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub client_info: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -112,6 +113,7 @@ impl InitializeRequest { /// /// See protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization) #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = INITIALIZE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -133,14 +135,14 @@ pub struct InitializeResponse { /// /// Note: in future versions of the protocol, this will be required. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub agent_info: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -192,6 +194,7 @@ impl InitializeResponse { /// Metadata about the implementation of the client or agent. /// Describes the name and version of an MCP implementation, with an optional /// title for UI representation. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -212,7 +215,7 @@ pub struct Implementation { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -254,6 +257,7 @@ impl Implementation { /// Request parameters for the authenticate method. /// /// Specifies which authentication method to use. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = AUTHENTICATE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -267,7 +271,7 @@ pub struct AuthenticateRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -293,6 +297,7 @@ impl AuthenticateRequest { } /// Response to the `authenticate` method. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = AUTHENTICATE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -303,7 +308,7 @@ pub struct AuthenticateResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -335,6 +340,7 @@ impl AuthenticateResponse { /// /// Terminates the current authenticated session. #[cfg(feature = "unstable_logout")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = LOGOUT_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -345,7 +351,7 @@ pub struct LogoutRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -374,6 +380,7 @@ impl LogoutRequest { /// /// Response to the `logout` method. #[cfg(feature = "unstable_logout")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = LOGOUT_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -384,7 +391,7 @@ pub struct LogoutResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -414,6 +421,7 @@ impl LogoutResponse { /// Authentication-related capabilities supported by the agent. #[cfg(feature = "unstable_logout")] #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -422,14 +430,14 @@ pub struct AgentAuthCapabilities { /// /// By supplying `{}` it means that the agent supports the logout method. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub logout: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -467,6 +475,7 @@ impl AgentAuthCapabilities { /// /// By supplying `{}` it means that the agent supports the logout method. #[cfg(feature = "unstable_logout")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[non_exhaustive] pub struct LogoutCapabilities { @@ -475,7 +484,7 @@ pub struct LogoutCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -597,6 +606,7 @@ impl AuthMethod { /// Agent handles authentication itself. /// /// This is the default authentication method type. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -606,14 +616,13 @@ pub struct AuthMethodAgent { /// Human-readable name of the authentication method. pub name: String, /// Optional description providing more details about this authentication method. - #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -655,6 +664,7 @@ impl AuthMethodAgent { /// /// The user provides credentials that the client passes to the agent as environment variables. #[cfg(feature = "unstable_auth_methods")] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -664,19 +674,17 @@ pub struct AuthMethodEnvVar { /// Human-readable name of the authentication method. pub name: String, /// Optional description providing more details about this authentication method. - #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, /// The environment variables the client should set. pub vars: Vec, /// Optional link to a page where the user can obtain their credentials. - #[serde(skip_serializing_if = "Option::is_none")] pub link: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -730,6 +738,7 @@ impl AuthMethodEnvVar { /// /// Describes a single environment variable for an [`AuthMethodEnvVar`] authentication method. #[cfg(feature = "unstable_auth_methods")] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -737,7 +746,6 @@ pub struct AuthEnvVar { /// The environment variable name (e.g. `"OPENAI_API_KEY"`). pub name: String, /// Human-readable label for this variable, displayed in client UI. - #[serde(skip_serializing_if = "Option::is_none")] pub label: Option, /// Whether this value is a secret (e.g. API key, token). /// Clients should use a password-style input for secret vars. @@ -757,7 +765,7 @@ pub struct AuthEnvVar { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -834,6 +842,7 @@ impl AuthEnvVar { /// /// The client runs an interactive terminal for the user to authenticate via a TUI. #[cfg(feature = "unstable_auth_methods")] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -843,7 +852,6 @@ pub struct AuthMethodTerminal { /// Human-readable name of the authentication method. pub name: String, /// Optional description providing more details about this authentication method. - #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, /// Additional arguments to pass when running the agent binary for terminal auth. #[serde(default, skip_serializing_if = "Vec::is_empty")] @@ -856,7 +864,7 @@ pub struct AuthMethodTerminal { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -912,6 +920,7 @@ impl AuthMethodTerminal { /// Request parameters for creating a new session. /// /// See protocol docs: [Creating a Session](https://agentclientprotocol.com/protocol/session-setup#creating-a-session) +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_NEW_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -938,7 +947,7 @@ pub struct NewSessionRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -989,6 +998,7 @@ impl NewSessionRequest { /// /// See protocol docs: [Creating a Session](https://agentclientprotocol.com/protocol/session-setup#creating-a-session) #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_NEW_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1002,7 +1012,7 @@ pub struct NewSessionResponse { /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub modes: Option, /// **UNSTABLE** /// @@ -1011,18 +1021,18 @@ pub struct NewSessionResponse { /// Initial model state if supported by the Agent #[cfg(feature = "unstable_session_model")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1089,6 +1099,7 @@ impl NewSessionResponse { /// Only available if the Agent supports the `loadSession` capability. /// /// See protocol docs: [Loading Sessions](https://agentclientprotocol.com/protocol/session-setup#loading-sessions) +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_LOAD_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1117,7 +1128,7 @@ pub struct LoadSessionRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1167,6 +1178,7 @@ impl LoadSessionRequest { /// Response from loading an existing session. #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_LOAD_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1176,7 +1188,7 @@ pub struct LoadSessionResponse { /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub modes: Option, /// **UNSTABLE** /// @@ -1185,18 +1197,18 @@ pub struct LoadSessionResponse { /// Initial model state if supported by the Agent #[cfg(feature = "unstable_session_model")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1262,6 +1274,7 @@ impl LoadSessionResponse { /// /// Only available if the Agent supports the `session.fork` capability. #[cfg(feature = "unstable_session_fork")] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_FORK_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1291,7 +1304,7 @@ pub struct ForkSessionRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1347,6 +1360,7 @@ impl ForkSessionRequest { /// Response from forking an existing session. #[cfg(feature = "unstable_session_fork")] #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_FORK_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1358,7 +1372,7 @@ pub struct ForkSessionResponse { /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub modes: Option, /// **UNSTABLE** /// @@ -1367,18 +1381,18 @@ pub struct ForkSessionResponse { /// Initial model state if supported by the Agent #[cfg(feature = "unstable_session_model")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1452,6 +1466,7 @@ impl ForkSessionResponse { /// /// Only available if the Agent supports the `sessionCapabilities.resume` capability. #[cfg(feature = "unstable_session_resume")] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_RESUME_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1481,7 +1496,7 @@ pub struct ResumeSessionRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1537,6 +1552,7 @@ impl ResumeSessionRequest { /// Response from resuming an existing session. #[cfg(feature = "unstable_session_resume")] #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_RESUME_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1546,7 +1562,7 @@ pub struct ResumeSessionResponse { /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub modes: Option, /// **UNSTABLE** /// @@ -1555,18 +1571,18 @@ pub struct ResumeSessionResponse { /// Initial model state if supported by the Agent #[cfg(feature = "unstable_session_model")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1634,6 +1650,7 @@ impl ResumeSessionResponse { /// /// Only available if the Agent supports the `sessionCapabilities.close` capability. #[cfg(feature = "unstable_session_close")] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_CLOSE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1646,7 +1663,7 @@ pub struct CloseSessionRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1678,6 +1695,7 @@ impl CloseSessionRequest { /// /// Response from closing a session. #[cfg(feature = "unstable_session_close")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_CLOSE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1688,7 +1706,7 @@ pub struct CloseSessionResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1716,13 +1734,13 @@ impl CloseSessionResponse { /// Request parameters for listing existing sessions. /// /// Only available if the Agent supports the `sessionCapabilities.list` capability. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_LIST_METHOD_NAME))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ListSessionsRequest { /// Filter sessions by working directory. Must be an absolute path. - #[serde(skip_serializing_if = "Option::is_none")] pub cwd: Option, /// **UNSTABLE** /// @@ -1736,14 +1754,13 @@ pub struct ListSessionsRequest { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub additional_directories: Vec, /// Opaque cursor token from a previous response's nextCursor field for cursor-based pagination - #[serde(skip_serializing_if = "Option::is_none")] pub cursor: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1793,6 +1810,7 @@ impl ListSessionsRequest { /// Response from listing sessions. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_LIST_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1803,14 +1821,13 @@ pub struct ListSessionsResponse { pub sessions: Vec, /// Opaque cursor token. If present, pass this in the next request's cursor parameter /// to fetch the next page. If absent, there are no more results. - #[serde(skip_serializing_if = "Option::is_none")] pub next_cursor: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1844,6 +1861,7 @@ impl ListSessionsResponse { /// Information about a session returned by session/list #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1865,18 +1883,18 @@ pub struct SessionInfo { /// Human-readable title for the session #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub title: Option, /// ISO 8601 timestamp of last activity #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub updated_at: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1936,6 +1954,7 @@ impl SessionInfo { /// The set of modes and the one currently active. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1950,7 +1969,7 @@ pub struct SessionModeState { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1982,20 +2001,21 @@ impl SessionModeState { /// A mode the agent can operate in. /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct SessionMode { pub id: SessionModeId, pub name: String, - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub description: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2043,6 +2063,7 @@ impl SessionModeId { } /// Request parameters for setting a session mode. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_MODE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -2057,7 +2078,7 @@ pub struct SetSessionModeRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2079,6 +2100,7 @@ impl SetSessionModeRequest { } /// Response to `session/set_mode` method. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_MODE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -2089,7 +2111,7 @@ pub struct SetSessionModeResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2156,6 +2178,7 @@ impl SessionConfigGroupId { } /// A possible value for a session configuration option. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -2165,14 +2188,14 @@ pub struct SessionConfigSelectOption { /// Human-readable label for this option value. pub name: String, /// Optional description for this option value. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub description: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2206,6 +2229,7 @@ impl SessionConfigSelectOption { } /// A group of possible values for a session configuration option. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -2221,7 +2245,7 @@ pub struct SessionConfigSelectGroup { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2364,6 +2388,7 @@ pub enum SessionConfigKind { /// A session configuration option selector and its current state. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -2373,11 +2398,11 @@ pub struct SessionConfigOption { /// Human-readable label for the option. pub name: String, /// Optional description for the Client to display to the user. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub description: Option, /// Optional semantic category for this option (UX only). #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub category: Option, /// Type-specific fields for this configuration option. #[serde(flatten)] @@ -2387,7 +2412,7 @@ pub struct SessionConfigOption { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2557,6 +2582,7 @@ impl From<&str> for SessionConfigOptionValue { } /// Request parameters for setting a session configuration option. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_CONFIG_OPTION_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -2581,7 +2607,7 @@ pub struct SetSessionConfigOptionRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2630,6 +2656,7 @@ impl SetSessionConfigOptionRequest { /// Response to `session/set_config_option` method. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_CONFIG_OPTION_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -2643,7 +2670,7 @@ pub struct SetSessionConfigOptionResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2696,6 +2723,7 @@ pub enum McpServer { } /// HTTP transport configuration for MCP. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -2711,7 +2739,7 @@ pub struct McpServerHttp { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2746,6 +2774,7 @@ impl McpServerHttp { } /// SSE transport configuration for MCP. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -2761,7 +2790,7 @@ pub struct McpServerSse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2796,6 +2825,7 @@ impl McpServerSse { } /// Stdio transport configuration for MCP. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -2813,7 +2843,7 @@ pub struct McpServerStdio { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2856,6 +2886,7 @@ impl McpServerStdio { } /// An environment variable to set when launching an MCP server. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -2869,7 +2900,7 @@ pub struct EnvVariable { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2896,6 +2927,7 @@ impl EnvVariable { } /// An HTTP header to set when making requests to the MCP server. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -2909,7 +2941,7 @@ pub struct HttpHeader { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -2942,6 +2974,7 @@ impl HttpHeader { /// Contains the user's message and any additional context. /// /// See protocol docs: [User Message](https://agentclientprotocol.com/protocol/prompt-turn#1-user-message) +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_PROMPT_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -2959,7 +2992,6 @@ pub struct PromptRequest { /// [`PromptResponse`] to confirm it was recorded. /// Both clients and agents MUST use UUID format for message IDs. #[cfg(feature = "unstable_message_id")] - #[serde(skip_serializing_if = "Option::is_none")] pub message_id: Option, /// The blocks of content that compose the user's message. /// @@ -2980,7 +3012,7 @@ pub struct PromptRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3028,6 +3060,7 @@ impl PromptRequest { /// /// See protocol docs: [Check for Completion](https://agentclientprotocol.com/protocol/prompt-turn#4-check-for-completion) #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_PROMPT_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -3043,7 +3076,6 @@ pub struct PromptResponse { /// to confirm it was recorded. If the client did not provide one, the agent MAY assign one /// and return it here. Absence of this field indicates the agent did not record a message ID. #[cfg(feature = "unstable_message_id")] - #[serde(skip_serializing_if = "Option::is_none")] pub user_message_id: Option, /// Indicates why the agent stopped processing the turn. pub stop_reason: StopReason, @@ -3054,14 +3086,14 @@ pub struct PromptResponse { /// Token usage for this turn (optional). #[cfg(feature = "unstable_session_usage")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub usage: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3151,6 +3183,7 @@ pub enum StopReason { /// /// Token usage information for a prompt turn. #[cfg(feature = "unstable_session_usage")] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -3162,13 +3195,10 @@ pub struct Usage { /// Total output tokens across all turns. pub output_tokens: u64, /// Total thought/reasoning tokens - #[serde(skip_serializing_if = "Option::is_none")] pub thought_tokens: Option, /// Total cache read tokens. - #[serde(skip_serializing_if = "Option::is_none")] pub cached_read_tokens: Option, /// Total cache write tokens. - #[serde(skip_serializing_if = "Option::is_none")] pub cached_write_tokens: Option, } @@ -3217,6 +3247,7 @@ impl Usage { /// The set of models and the one currently active. #[cfg(feature = "unstable_session_model")] #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -3231,7 +3262,7 @@ pub struct SessionModelState { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3284,6 +3315,7 @@ impl ModelId { /// /// Information about a selectable model. #[cfg(feature = "unstable_session_model")] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -3293,14 +3325,14 @@ pub struct ModelInfo { /// Human-readable name of the model. pub name: String, /// Optional description of the model. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub description: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3341,6 +3373,7 @@ impl ModelInfo { /// /// Request parameters for setting a session model. #[cfg(feature = "unstable_session_model")] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_MODEL_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -3355,7 +3388,7 @@ pub struct SetSessionModelRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3388,6 +3421,7 @@ impl SetSessionModelRequest { /// /// Response to `session/set_model` method. #[cfg(feature = "unstable_session_model")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_MODEL_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -3398,7 +3432,7 @@ pub struct SetSessionModelResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3489,6 +3523,7 @@ impl ProviderCurrentConfig { /// Information about a configurable LLM provider. #[cfg(feature = "unstable_llm_providers")] #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -3509,7 +3544,7 @@ pub struct ProviderInfo { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3549,6 +3584,7 @@ impl ProviderInfo { /// /// Request parameters for `providers/list`. #[cfg(feature = "unstable_llm_providers")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_LIST_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -3559,7 +3595,7 @@ pub struct ListProvidersRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3589,6 +3625,7 @@ impl ListProvidersRequest { /// Response to `providers/list`. #[cfg(feature = "unstable_llm_providers")] #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_LIST_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -3602,7 +3639,7 @@ pub struct ListProvidersResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3636,6 +3673,7 @@ impl ListProvidersResponse { /// /// Replaces the full configuration for one provider id. #[cfg(feature = "unstable_llm_providers")] +#[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_SET_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -3656,7 +3694,7 @@ pub struct SetProvidersRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3699,6 +3737,7 @@ impl SetProvidersRequest { /// /// Response to `providers/set`. #[cfg(feature = "unstable_llm_providers")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_SET_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -3709,7 +3748,7 @@ pub struct SetProvidersResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3738,6 +3777,7 @@ impl SetProvidersResponse { /// /// Request parameters for `providers/disable`. #[cfg(feature = "unstable_llm_providers")] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_DISABLE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -3750,7 +3790,7 @@ pub struct DisableProvidersRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3782,6 +3822,7 @@ impl DisableProvidersRequest { /// /// Response to `providers/disable`. #[cfg(feature = "unstable_llm_providers")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_DISABLE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -3792,7 +3833,7 @@ pub struct DisableProvidersResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3824,6 +3865,7 @@ impl DisableProvidersResponse { /// /// See protocol docs: [Agent Capabilities](https://agentclientprotocol.com/protocol/initialization#agent-capabilities) #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -3856,7 +3898,7 @@ pub struct AgentCapabilities { /// By supplying `{}` it means that the agent supports provider configuration methods. #[cfg(feature = "unstable_llm_providers")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub providers: Option, /// **UNSTABLE** /// @@ -3865,7 +3907,7 @@ pub struct AgentCapabilities { /// NES (Next Edit Suggestions) capabilities supported by the agent. #[cfg(feature = "unstable_nes")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub nes: Option, /// **UNSTABLE** /// @@ -3874,14 +3916,14 @@ pub struct AgentCapabilities { /// The position encoding selected by the agent from the client's supported encodings. #[cfg(feature = "unstable_nes")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub position_encoding: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -3988,6 +4030,7 @@ impl AgentCapabilities { /// /// By supplying `{}` it means that the agent supports provider configuration methods. #[cfg(feature = "unstable_llm_providers")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[non_exhaustive] pub struct ProvidersCapabilities { @@ -3996,7 +4039,7 @@ pub struct ProvidersCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -4029,13 +4072,14 @@ impl ProvidersCapabilities { /// /// See protocol docs: [Session Capabilities](https://agentclientprotocol.com/protocol/initialization#session-capabilities) #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct SessionCapabilities { /// Whether the agent supports `session/list`. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub list: Option, /// **UNSTABLE** /// @@ -4044,7 +4088,7 @@ pub struct SessionCapabilities { /// Whether the agent supports `additionalDirectories` on supported session lifecycle requests and `session/list`. #[cfg(feature = "unstable_session_additional_directories")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub additional_directories: Option, /// **UNSTABLE** /// @@ -4053,7 +4097,7 @@ pub struct SessionCapabilities { /// Whether the agent supports `session/fork`. #[cfg(feature = "unstable_session_fork")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub fork: Option, /// **UNSTABLE** /// @@ -4062,7 +4106,7 @@ pub struct SessionCapabilities { /// Whether the agent supports `session/resume`. #[cfg(feature = "unstable_session_resume")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub resume: Option, /// **UNSTABLE** /// @@ -4071,14 +4115,14 @@ pub struct SessionCapabilities { /// Whether the agent supports `session/close`. #[cfg(feature = "unstable_session_close")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub close: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -4149,6 +4193,7 @@ impl SessionCapabilities { /// Capabilities for the `session/list` method. /// /// By supplying `{}` it means that the agent supports listing of sessions. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[non_exhaustive] pub struct SessionListCapabilities { @@ -4157,7 +4202,7 @@ pub struct SessionListCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -4188,6 +4233,7 @@ impl SessionListCapabilities { /// By supplying `{}` it means that the agent supports the `additionalDirectories` field on /// supported session lifecycle requests and `session/list`. #[cfg(feature = "unstable_session_additional_directories")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[non_exhaustive] pub struct SessionAdditionalDirectoriesCapabilities { @@ -4196,7 +4242,7 @@ pub struct SessionAdditionalDirectoriesCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -4227,6 +4273,7 @@ impl SessionAdditionalDirectoriesCapabilities { /// /// By supplying `{}` it means that the agent supports forking of sessions. #[cfg(feature = "unstable_session_fork")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[non_exhaustive] pub struct SessionForkCapabilities { @@ -4235,7 +4282,7 @@ pub struct SessionForkCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -4266,6 +4313,7 @@ impl SessionForkCapabilities { /// /// By supplying `{}` it means that the agent supports resuming of sessions. #[cfg(feature = "unstable_session_resume")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[non_exhaustive] pub struct SessionResumeCapabilities { @@ -4274,7 +4322,7 @@ pub struct SessionResumeCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -4305,6 +4353,7 @@ impl SessionResumeCapabilities { /// /// By supplying `{}` it means that the agent supports closing of sessions. #[cfg(feature = "unstable_session_close")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[non_exhaustive] pub struct SessionCloseCapabilities { @@ -4313,7 +4362,7 @@ pub struct SessionCloseCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -4348,6 +4397,7 @@ impl SessionCloseCapabilities { /// the agent can process. /// /// See protocol docs: [Prompt Capabilities](https://agentclientprotocol.com/protocol/initialization#prompt-capabilities) +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -4369,7 +4419,7 @@ pub struct PromptCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -4416,6 +4466,7 @@ impl PromptCapabilities { } /// MCP capabilities supported by the agent +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -4431,7 +4482,7 @@ pub struct McpCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -5020,6 +5071,7 @@ impl ClientNotification { /// Notification to cancel ongoing operations for a session. /// /// See protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/prompt-turn#cancellation) +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = SESSION_CANCEL_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -5032,7 +5084,7 @@ pub struct CancelNotification { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } diff --git a/src/client.rs b/src/client.rs index 8692efa3..4ba85b13 100644 --- a/src/client.rs +++ b/src/client.rs @@ -8,7 +8,7 @@ use std::{path::PathBuf, sync::Arc}; use derive_more::{Display, From}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::{DefaultOnError, VecSkipError, serde_as}; +use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; #[cfg(feature = "unstable_elicitation")] use crate::elicitation::{ @@ -31,6 +31,7 @@ use crate::{ClientNesCapabilities, PositionEncodingKind}; /// Used to stream real-time progress and results during prompt processing. /// /// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output) +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] #[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))] #[serde(rename_all = "camelCase")] @@ -45,7 +46,7 @@ pub struct SessionNotification { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -116,6 +117,7 @@ pub enum SessionUpdate { /// The current mode of the session has changed /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -127,7 +129,7 @@ pub struct CurrentModeUpdate { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -154,6 +156,7 @@ impl CurrentModeUpdate { /// Session configuration options have been updated. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -166,7 +169,7 @@ pub struct ConfigOptionUpdate { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -195,6 +198,7 @@ impl ConfigOptionUpdate { /// /// Agents send this notification to update session information like title or custom metadata. /// This allows clients to display dynamic session names and track session state changes. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -210,7 +214,7 @@ pub struct SessionInfoUpdate { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -253,6 +257,7 @@ impl SessionInfoUpdate { /// Context window and cost update for a session. #[cfg(feature = "unstable_session_usage")] #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -263,14 +268,14 @@ pub struct UsageUpdate { pub size: u64, /// Cumulative session cost (optional). #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub cost: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -333,6 +338,7 @@ impl Cost { } /// A streamed item of content +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -349,14 +355,13 @@ pub struct ContentChunk { /// A change in `messageId` indicates a new message has started. /// Both clients and agents MUST use UUID format for message IDs. #[cfg(feature = "unstable_message_id")] - #[serde(skip_serializing_if = "Option::is_none")] pub message_id: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -401,6 +406,7 @@ impl ContentChunk { /// Available commands are ready or have changed #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -413,7 +419,7 @@ pub struct AvailableCommandsUpdate { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -440,6 +446,7 @@ impl AvailableCommandsUpdate { /// Information about a command. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -457,7 +464,7 @@ pub struct AvailableCommand { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -501,6 +508,7 @@ pub enum AvailableCommandInput { } /// All text that was typed after the command name is provided as input. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -512,7 +520,7 @@ pub struct UnstructuredCommandInput { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -544,6 +552,7 @@ impl UnstructuredCommandInput { /// Sent when the agent needs authorization before performing a sensitive operation. /// /// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission) +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] #[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -560,7 +569,7 @@ pub struct RequestPermissionRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -592,6 +601,7 @@ impl RequestPermissionRequest { } /// An option presented to the user when requesting permission. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -607,7 +617,7 @@ pub struct PermissionOption { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -670,6 +680,7 @@ pub enum PermissionOptionKind { } /// Response to a permission request. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -683,7 +694,7 @@ pub struct RequestPermissionResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -728,6 +739,7 @@ pub enum RequestPermissionOutcome { } /// The user selected one of the provided options. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -739,7 +751,7 @@ pub struct SelectedPermissionOutcome { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -769,6 +781,7 @@ impl SelectedPermissionOutcome { /// Request to write content to a text file. /// /// Only available if the client supports the `fs.writeTextFile` capability. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -785,7 +798,7 @@ pub struct WriteTextFileRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -817,6 +830,7 @@ impl WriteTextFileRequest { } /// Response to `fs/write_text_file` +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))] @@ -827,7 +841,7 @@ pub struct WriteTextFileResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -854,6 +868,7 @@ impl WriteTextFileResponse { /// Request to read content from a text file. /// /// Only available if the client supports the `fs.readTextFile` capability. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -864,17 +879,15 @@ pub struct ReadTextFileRequest { /// Absolute path to the file to read. pub path: PathBuf, /// Line number to start reading from (1-based). - #[serde(skip_serializing_if = "Option::is_none")] pub line: Option, /// Maximum number of lines to read. - #[serde(skip_serializing_if = "Option::is_none")] pub limit: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -917,6 +930,7 @@ impl ReadTextFileRequest { } /// Response containing the contents of a text file. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -928,7 +942,7 @@ pub struct ReadTextFileResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -969,6 +983,7 @@ impl TerminalId { } /// Request to create a new terminal and execute a command. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME))] @@ -985,7 +1000,6 @@ pub struct CreateTerminalRequest { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub env: Vec, /// Working directory for the command (absolute path). - #[serde(skip_serializing_if = "Option::is_none")] pub cwd: Option, /// Maximum number of output bytes to retain. /// @@ -995,14 +1009,13 @@ pub struct CreateTerminalRequest { /// The Client MUST ensure truncation happens at a character boundary to maintain valid /// string output, even if this means the retained output is slightly less than the /// specified limit. - #[serde(skip_serializing_if = "Option::is_none")] pub output_byte_limit: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1068,6 +1081,7 @@ impl CreateTerminalRequest { } /// Response containing the ID of the created terminal. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME))] @@ -1080,7 +1094,7 @@ pub struct CreateTerminalResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1106,6 +1120,7 @@ impl CreateTerminalResponse { } /// Request to get the current output and status of a terminal. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME))] @@ -1120,7 +1135,7 @@ pub struct TerminalOutputRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1147,6 +1162,7 @@ impl TerminalOutputRequest { } /// Response containing the terminal output and exit status. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME))] @@ -1163,7 +1179,7 @@ pub struct TerminalOutputResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1198,6 +1214,7 @@ impl TerminalOutputResponse { } /// Request to release a terminal and free its resources. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME))] @@ -1212,7 +1229,7 @@ pub struct ReleaseTerminalRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1239,6 +1256,7 @@ impl ReleaseTerminalRequest { } /// Response to terminal/release method +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME))] @@ -1249,7 +1267,7 @@ pub struct ReleaseTerminalResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1272,6 +1290,7 @@ impl ReleaseTerminalResponse { } /// Request to kill a terminal without releasing it. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME))] @@ -1286,7 +1305,7 @@ pub struct KillTerminalRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1313,6 +1332,7 @@ impl KillTerminalRequest { } /// Response to `terminal/kill` method +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME))] @@ -1323,7 +1343,7 @@ pub struct KillTerminalResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1346,6 +1366,7 @@ impl KillTerminalResponse { } /// Request to wait for a terminal command to exit. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME))] @@ -1360,7 +1381,7 @@ pub struct WaitForTerminalExitRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1387,6 +1408,7 @@ impl WaitForTerminalExitRequest { } /// Response containing the exit status of a terminal command. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME))] @@ -1400,7 +1422,7 @@ pub struct WaitForTerminalExitResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1426,6 +1448,7 @@ impl WaitForTerminalExitResponse { } /// Exit status of a terminal command. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1439,7 +1462,7 @@ pub struct TerminalExitStatus { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1484,6 +1507,7 @@ impl TerminalExitStatus { /// /// See protocol docs: [Client Capabilities](https://agentclientprotocol.com/protocol/initialization#client-capabilities) #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1513,7 +1537,7 @@ pub struct ClientCapabilities { /// Determines which elicitation modes the agent may use. #[cfg(feature = "unstable_elicitation")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub elicitation: Option, /// **UNSTABLE** /// @@ -1522,7 +1546,7 @@ pub struct ClientCapabilities { /// NES (Next Edit Suggestions) capabilities supported by the client. #[cfg(feature = "unstable_nes")] #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub nes: Option, /// **UNSTABLE** /// @@ -1539,7 +1563,7 @@ pub struct ClientCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1633,6 +1657,7 @@ impl ClientCapabilities { /// method types the client can handle. This governs opt-in types that require /// additional client-side support. #[cfg(feature = "unstable_auth_methods")] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1647,7 +1672,7 @@ pub struct AuthCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1683,6 +1708,7 @@ impl AuthCapabilities { /// File system capabilities that a client may support. /// /// See protocol docs: [FileSystem](https://agentclientprotocol.com/protocol/initialization#filesystem) +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1698,7 +1724,7 @@ pub struct FileSystemCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } diff --git a/src/content.rs b/src/content.rs index a1ab6559..f511962f 100644 --- a/src/content.rs +++ b/src/content.rs @@ -11,7 +11,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::{DefaultOnError, VecSkipError, serde_as}; +use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; use crate::{IntoOption, Meta}; @@ -61,11 +61,12 @@ pub enum ContentBlock { /// Text provided to or from an LLM. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[non_exhaustive] pub struct TextContent { #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub annotations: Option, pub text: String, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -73,7 +74,7 @@ pub struct TextContent { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -113,23 +114,23 @@ impl> From for ContentBlock { /// An image provided to or from an LLM. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ImageContent { #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub annotations: Option, pub data: String, pub mime_type: String, - #[serde(skip_serializing_if = "Option::is_none")] pub uri: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -171,12 +172,13 @@ impl ImageContent { /// Audio provided to or from an LLM. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct AudioContent { #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub annotations: Option, pub data: String, pub mime_type: String, @@ -185,7 +187,7 @@ pub struct AudioContent { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -220,11 +222,12 @@ impl AudioContent { /// The contents of a resource, embedded into a prompt or tool call result. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[non_exhaustive] pub struct EmbeddedResource { #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub annotations: Option, pub resource: EmbeddedResourceResource, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -232,7 +235,7 @@ pub struct EmbeddedResource { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -274,11 +277,11 @@ pub enum EmbeddedResourceResource { } /// Text-based resource contents. +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct TextResourceContents { - #[serde(skip_serializing_if = "Option::is_none")] pub mime_type: Option, pub text: String, pub uri: String, @@ -287,7 +290,7 @@ pub struct TextResourceContents { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -321,12 +324,12 @@ impl TextResourceContents { } /// Binary resource contents. +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct BlobResourceContents { pub blob: String, - #[serde(skip_serializing_if = "Option::is_none")] pub mime_type: Option, pub uri: String, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -334,7 +337,7 @@ pub struct BlobResourceContents { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -369,21 +372,18 @@ impl BlobResourceContents { /// A resource that the server is capable of reading, included in a prompt or tool call result. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ResourceLink { #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub annotations: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub mime_type: Option, pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] pub size: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, pub uri: String, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -391,7 +391,7 @@ pub struct ResourceLink { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -454,23 +454,22 @@ impl ResourceLink { /// Optional annotations for the client. The client can use annotations to inform how objects are used or displayed #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, Default)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Annotations { #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub audience: Option>, - #[serde(skip_serializing_if = "Option::is_none")] pub last_modified: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub priority: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } diff --git a/src/elicitation.rs b/src/elicitation.rs index 249cb0f0..6c8b6180 100644 --- a/src/elicitation.rs +++ b/src/elicitation.rs @@ -10,7 +10,7 @@ use std::{collections::BTreeMap, sync::Arc}; use derive_more::{Display, From}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::{DefaultOnError, serde_as}; +use serde_with::{DefaultOnError, serde_as, skip_serializing_none}; use crate::client::{ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME}; use crate::tool_call::ToolCallId; @@ -85,36 +85,30 @@ impl EnumOption { /// /// When `enum` or `oneOf` is set, this represents a single-select enum /// with `"type": "string"`. +#[skip_serializing_none] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct StringPropertySchema { /// Optional title for the property. - #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, /// Human-readable description. - #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, /// Minimum string length. - #[serde(skip_serializing_if = "Option::is_none")] pub min_length: Option, /// Maximum string length. - #[serde(skip_serializing_if = "Option::is_none")] pub max_length: Option, /// Pattern the string must match. - #[serde(skip_serializing_if = "Option::is_none")] pub pattern: Option, /// String format. - #[serde(skip_serializing_if = "Option::is_none")] pub format: Option, /// Default value. - #[serde(skip_serializing_if = "Option::is_none")] pub default: Option, /// Enum values for untitled single-select enums. - #[serde(rename = "enum", skip_serializing_if = "Option::is_none")] + #[serde(rename = "enum")] pub enum_values: Option>, /// Titled enum options for titled single-select enums. - #[serde(rename = "oneOf", skip_serializing_if = "Option::is_none")] + #[serde(rename = "oneOf")] pub one_of: Option>, } @@ -226,24 +220,20 @@ impl StringPropertySchema { } /// Schema for number (floating-point) properties in an elicitation form. +#[skip_serializing_none] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NumberPropertySchema { /// Optional title for the property. - #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, /// Human-readable description. - #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, /// Minimum value (inclusive). - #[serde(skip_serializing_if = "Option::is_none")] pub minimum: Option, /// Maximum value (inclusive). - #[serde(skip_serializing_if = "Option::is_none")] pub maximum: Option, /// Default value. - #[serde(skip_serializing_if = "Option::is_none")] pub default: Option, } @@ -291,24 +281,20 @@ impl NumberPropertySchema { } /// Schema for integer properties in an elicitation form. +#[skip_serializing_none] #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct IntegerPropertySchema { /// Optional title for the property. - #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, /// Human-readable description. - #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, /// Minimum value (inclusive). - #[serde(skip_serializing_if = "Option::is_none")] pub minimum: Option, /// Maximum value (inclusive). - #[serde(skip_serializing_if = "Option::is_none")] pub maximum: Option, /// Default value. - #[serde(skip_serializing_if = "Option::is_none")] pub default: Option, } @@ -356,18 +342,16 @@ impl IntegerPropertySchema { } /// Schema for boolean properties in an elicitation form. +#[skip_serializing_none] #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct BooleanPropertySchema { /// Optional title for the property. - #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, /// Human-readable description. - #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, /// Default value. - #[serde(skip_serializing_if = "Option::is_none")] pub default: Option, } @@ -451,26 +435,22 @@ pub enum MultiSelectItems { } /// Schema for multi-select (array) properties in an elicitation form. +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct MultiSelectPropertySchema { /// Optional title for the property. - #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, /// Human-readable description. - #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, /// Minimum number of items to select. - #[serde(skip_serializing_if = "Option::is_none")] pub min_items: Option, /// Maximum number of items to select. - #[serde(skip_serializing_if = "Option::is_none")] pub max_items: Option, /// The items definition describing allowed values. pub items: MultiSelectItems, /// Default selected values. - #[serde(skip_serializing_if = "Option::is_none")] pub default: Option>, } @@ -600,6 +580,7 @@ fn default_object_type() -> ElicitationSchemaType { /// /// This represents a JSON Schema object with primitive-typed properties, /// as required by the elicitation specification. +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -608,16 +589,13 @@ pub struct ElicitationSchema { #[serde(rename = "type", default = "default_object_type")] pub type_: ElicitationSchemaType, /// Optional title for the schema. - #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, /// Property definitions (must be primitive types). #[serde(default)] pub properties: BTreeMap, /// List of required property names. - #[serde(skip_serializing_if = "Option::is_none")] pub required: Option>, /// Optional description of what this schema represents. - #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, } @@ -742,24 +720,25 @@ impl ElicitationSchema { /// /// Elicitation capabilities supported by the client. #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ElicitationCapabilities { /// Whether the client supports form-based elicitation. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub form: Option, /// Whether the client supports URL-based elicitation. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub url: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -800,6 +779,7 @@ impl ElicitationCapabilities { /// This capability is not part of the spec yet, and may be removed or changed at any point. /// /// Form-based elicitation capabilities. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -809,7 +789,7 @@ pub struct ElicitationFormCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -836,6 +816,7 @@ impl ElicitationFormCapabilities { /// This capability is not part of the spec yet, and may be removed or changed at any point. /// /// URL-based elicitation capabilities. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -845,7 +826,7 @@ pub struct ElicitationUrlCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -892,6 +873,7 @@ pub enum ElicitationScope { /// When `tool_call_id` is set, the elicitation is tied to a specific tool call. /// This is useful when an agent receives an elicitation from an MCP server /// during a tool call and needs to redirect it to the user. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -899,7 +881,6 @@ pub struct ElicitationSessionScope { /// The session this elicitation is tied to. pub session_id: SessionId, /// Optional tool call within the session. - #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, } @@ -963,6 +944,7 @@ impl From for ElicitationScope { /// The agent sends this to the client to request information from the user, /// either via a form or by directing them to a URL. /// Elicitations are tied to a session (optionally a tool call) or a request. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] #[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -978,7 +960,7 @@ pub struct CreateElicitationRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1114,6 +1096,7 @@ impl ElicitationUrlMode { /// This capability is not part of the spec yet, and may be removed or changed at any point. /// /// Response from the client to an elicitation request. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] #[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1127,7 +1110,7 @@ pub struct CreateElicitationResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1172,12 +1155,13 @@ pub enum ElicitationAction { /// This capability is not part of the spec yet, and may be removed or changed at any point. /// /// The user accepted the elicitation and provided content. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ElicitationAcceptAction { /// The user-provided content, if any, as an object matching the requested schema. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub content: Option>, } @@ -1268,6 +1252,7 @@ impl Default for ElicitationAcceptAction { /// This capability is not part of the spec yet, and may be removed or changed at any point. /// /// Notification sent by the agent when a URL-based elicitation is complete. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))] #[serde(rename_all = "camelCase")] @@ -1280,7 +1265,7 @@ pub struct CompleteElicitationNotification { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } diff --git a/src/error.rs b/src/error.rs index 8606d056..21291473 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,6 +14,7 @@ use std::{fmt::Display, str}; use schemars::{JsonSchema, Schema}; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use crate::IntoOption; @@ -25,6 +26,7 @@ pub type Result = std::result::Result; /// JSON-RPC 2.0 error object specification with optional additional data. /// /// See protocol docs: [JSON-RPC Error Object](https://www.jsonrpc.org/specification#error_object) +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[non_exhaustive] pub struct Error { @@ -36,7 +38,6 @@ pub struct Error { pub message: String, /// Optional primitive or structured value that contains additional information about the error. /// This may include debugging information or context-specific details. - #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, } diff --git a/src/nes.rs b/src/nes.rs index 26b851c9..24ce9f07 100644 --- a/src/nes.rs +++ b/src/nes.rs @@ -6,7 +6,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::{DefaultOnError, VecSkipError, serde_as}; +use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; use crate::{IntoOption, Meta, SessionId}; @@ -94,24 +94,25 @@ impl Range { /// NES capabilities advertised by the agent during initialization. #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesCapabilities { /// Events the agent wants to receive. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub events: Option, /// Context the agent wants attached to each suggestion request. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub context: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -147,20 +148,21 @@ impl NesCapabilities { /// Event capabilities the agent can consume. #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesEventCapabilities { /// Document event capabilities. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub document: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -190,36 +192,37 @@ impl NesEventCapabilities { /// Document event capabilities the agent wants to receive. #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesDocumentEventCapabilities { /// Whether the agent wants `document/didOpen` events. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub did_open: Option, /// Whether the agent wants `document/didChange` events, and the sync kind. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub did_change: Option, /// Whether the agent wants `document/didClose` events. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub did_close: Option, /// Whether the agent wants `document/didSave` events. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub did_save: Option, /// Whether the agent wants `document/didFocus` events. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub did_focus: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -281,6 +284,7 @@ impl NesDocumentEventCapabilities { } /// Marker for `document/didOpen` capability support. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -290,7 +294,7 @@ pub struct NesDocumentDidOpenCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -302,6 +306,7 @@ impl NesDocumentDidOpenCapabilities { } /// Capabilities for `document/didChange` events. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -313,7 +318,7 @@ pub struct NesDocumentDidChangeCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -351,6 +356,7 @@ pub enum TextDocumentSyncKind { } /// Marker for `document/didClose` capability support. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -360,7 +366,7 @@ pub struct NesDocumentDidCloseCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -372,6 +378,7 @@ impl NesDocumentDidCloseCapabilities { } /// Marker for `document/didSave` capability support. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -381,7 +388,7 @@ pub struct NesDocumentDidSaveCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -393,6 +400,7 @@ impl NesDocumentDidSaveCapabilities { } /// Marker for `document/didFocus` capability support. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -402,7 +410,7 @@ pub struct NesDocumentDidFocusCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -415,40 +423,41 @@ impl NesDocumentDidFocusCapabilities { /// Context capabilities the agent wants attached to each suggestion request. #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesContextCapabilities { /// Whether the agent wants recent files context. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub recent_files: Option, /// Whether the agent wants related snippets context. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub related_snippets: Option, /// Whether the agent wants edit history context. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub edit_history: Option, /// Whether the agent wants user actions context. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub user_actions: Option, /// Whether the agent wants open files context. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub open_files: Option, /// Whether the agent wants diagnostics context. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub diagnostics: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -519,19 +528,19 @@ impl NesContextCapabilities { } /// Capabilities for recent files context. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesRecentFilesCapabilities { /// Maximum number of recent files the agent can use. - #[serde(skip_serializing_if = "Option::is_none")] pub max_count: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -543,6 +552,7 @@ impl NesRecentFilesCapabilities { } /// Capabilities for related snippets context. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -552,7 +562,7 @@ pub struct NesRelatedSnippetsCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -564,19 +574,19 @@ impl NesRelatedSnippetsCapabilities { } /// Capabilities for edit history context. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesEditHistoryCapabilities { /// Maximum number of edit history entries the agent can use. - #[serde(skip_serializing_if = "Option::is_none")] pub max_count: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -588,19 +598,19 @@ impl NesEditHistoryCapabilities { } /// Capabilities for user actions context. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesUserActionsCapabilities { /// Maximum number of user actions the agent can use. - #[serde(skip_serializing_if = "Option::is_none")] pub max_count: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -612,6 +622,7 @@ impl NesUserActionsCapabilities { } /// Capabilities for open files context. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -621,7 +632,7 @@ pub struct NesOpenFilesCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -633,6 +644,7 @@ impl NesOpenFilesCapabilities { } /// Capabilities for diagnostics context. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -642,7 +654,7 @@ pub struct NesDiagnosticsCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -657,28 +669,29 @@ impl NesDiagnosticsCapabilities { /// NES capabilities advertised by the client during initialization. #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ClientNesCapabilities { /// Whether the client supports the `jump` suggestion kind. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub jump: Option, /// Whether the client supports the `rename` suggestion kind. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub rename: Option, /// Whether the client supports the `searchAndReplace` suggestion kind. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub search_and_replace: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -722,6 +735,7 @@ impl ClientNesCapabilities { } /// Marker for jump suggestion support. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -731,7 +745,7 @@ pub struct NesJumpCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -743,6 +757,7 @@ impl NesJumpCapabilities { } /// Marker for rename suggestion support. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -752,7 +767,7 @@ pub struct NesRenameCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -764,6 +779,7 @@ impl NesRenameCapabilities { } /// Marker for search and replace suggestion support. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -773,7 +789,7 @@ pub struct NesSearchAndReplaceCapabilities { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -787,6 +803,7 @@ impl NesSearchAndReplaceCapabilities { // Document event notifications (client -> agent) /// Notification sent when a file is opened in the editor. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = DOCUMENT_DID_OPEN_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -807,7 +824,7 @@ pub struct DidOpenDocumentNotification { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -843,6 +860,7 @@ impl DidOpenDocumentNotification { } /// Notification sent when a file is edited. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = DOCUMENT_DID_CHANGE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -861,7 +879,7 @@ pub struct DidChangeDocumentNotification { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -898,12 +916,12 @@ impl DidChangeDocumentNotification { /// /// When `range` is `None`, `text` is the full content of the document. /// When `range` is `Some`, `text` replaces the given range. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct TextDocumentContentChangeEvent { /// The range of the document that changed. If `None`, the entire content is replaced. - #[serde(skip_serializing_if = "Option::is_none")] pub range: Option, /// The new text for the range, or the full document content if `range` is `None`. pub text: String, @@ -928,6 +946,7 @@ impl TextDocumentContentChangeEvent { } /// Notification sent when a file is closed. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = DOCUMENT_DID_CLOSE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -942,7 +961,7 @@ pub struct DidCloseDocumentNotification { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -969,6 +988,7 @@ impl DidCloseDocumentNotification { } /// Notification sent when a file is saved. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = DOCUMENT_DID_SAVE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -983,7 +1003,7 @@ pub struct DidSaveDocumentNotification { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1010,6 +1030,7 @@ impl DidSaveDocumentNotification { } /// Notification sent when a file becomes the active editor tab. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = DOCUMENT_DID_FOCUS_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1030,7 +1051,7 @@ pub struct DidFocusDocumentNotification { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1069,28 +1090,28 @@ impl DidFocusDocumentNotification { /// Request to start an NES session. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_START_METHOD_NAME))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct StartNesRequest { /// The root URI of the workspace. - #[serde(skip_serializing_if = "Option::is_none")] pub workspace_uri: Option, /// The workspace folders. #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub workspace_folders: Option>, /// Repository metadata, if the workspace is a git repository. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub repository: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1194,6 +1215,7 @@ impl NesRepository { } /// Response to `nes/start`. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_START_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1206,7 +1228,7 @@ pub struct StartNesResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1237,6 +1259,7 @@ impl StartNesResponse { /// /// The agent **must** cancel any ongoing work related to the NES session /// and then free up any resources associated with the session. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_CLOSE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1249,7 +1272,7 @@ pub struct CloseNesRequest { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1275,6 +1298,7 @@ impl CloseNesRequest { } /// Response from closing an NES session. +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_CLOSE_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1285,7 +1309,7 @@ pub struct CloseNesResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1326,6 +1350,7 @@ pub enum NesTriggerKind { /// Request for a code suggestion. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_SUGGEST_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1341,20 +1366,20 @@ pub struct SuggestNesRequest { pub position: Position, /// The current text selection range, if any. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub selection: Option, /// What triggered this suggestion request. pub trigger_kind: NesTriggerKind, /// Context for the suggestion, included based on agent capabilities. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub context: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1405,40 +1430,41 @@ impl SuggestNesRequest { /// Context attached to a suggestion request. #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct NesSuggestContext { /// Recently accessed files. #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub recent_files: Option>, /// Related code snippets. #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub related_snippets: Option>, /// Recent edit history. #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub edit_history: Option>, /// Recent user actions (typing, navigation, etc.). #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub user_actions: Option>, /// Currently open files in the editor. #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub open_files: Option>, /// Current diagnostics (errors, warnings). #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub diagnostics: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1627,6 +1653,7 @@ impl NesUserAction { /// An open file in the editor. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1637,11 +1664,11 @@ pub struct NesOpenFile { pub language_id: String, /// The visible range in the editor, if any. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub visible_range: Option, /// Timestamp in milliseconds since epoch of when the file was last focused. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub last_focused_ms: Option, } @@ -1723,6 +1750,7 @@ pub enum NesDiagnosticSeverity { /// Response to `nes/suggest`. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_SUGGEST_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1736,7 +1764,7 @@ pub struct SuggestNesResponse { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1779,6 +1807,7 @@ pub enum NesSuggestion { /// A text edit suggestion. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1791,7 +1820,7 @@ pub struct NesEditSuggestion { pub edits: Vec, /// Optional suggested cursor position after applying edits. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub cursor_position: Option, } @@ -1891,6 +1920,7 @@ impl NesRenameSuggestion { } /// A search-and-replace suggestion. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -1904,7 +1934,6 @@ pub struct NesSearchAndReplaceSuggestion { /// The replacement text. pub replace: String, /// Whether `search` is a regular expression. Defaults to `false`. - #[serde(skip_serializing_if = "Option::is_none")] pub is_regex: Option, } @@ -1935,6 +1964,7 @@ impl NesSearchAndReplaceSuggestion { // NES accept/reject notifications /// Notification sent when a suggestion is accepted. +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_ACCEPT_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1949,7 +1979,7 @@ pub struct AcceptNesNotification { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -1977,6 +2007,7 @@ impl AcceptNesNotification { /// Notification sent when a suggestion is rejected. #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "agent", "x-method" = NES_REJECT_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -1988,14 +2019,14 @@ pub struct RejectNesNotification { pub id: String, /// The reason for rejection. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub reason: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } diff --git a/src/plan.rs b/src/plan.rs index 21cc0ac4..7ca804b8 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -7,7 +7,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::{VecSkipError, serde_as}; +use serde_with::{VecSkipError, serde_as, skip_serializing_none}; use crate::{IntoOption, Meta}; @@ -19,6 +19,7 @@ use crate::{IntoOption, Meta}; /// /// See protocol docs: [Agent Plan](https://agentclientprotocol.com/protocol/agent-plan) #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -34,7 +35,7 @@ pub struct Plan { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -64,6 +65,7 @@ impl Plan { /// Represents a task or goal that the assistant intends to accomplish /// as part of fulfilling the user's request. /// See protocol docs: [Plan Entries](https://agentclientprotocol.com/protocol/agent-plan#plan-entries) +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -80,7 +82,7 @@ pub struct PlanEntry { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } diff --git a/src/protocol_level.rs b/src/protocol_level.rs index c386650f..0fbb45b5 100644 --- a/src/protocol_level.rs +++ b/src/protocol_level.rs @@ -1,5 +1,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use crate::{IntoOption, Meta, RequestId}; @@ -11,6 +12,7 @@ use crate::{IntoOption, Meta, RequestId}; /// /// See protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/cancellation) #[cfg(feature = "unstable_cancel_request")] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[schemars(extend("x-side" = "protocol", "x-method" = CANCEL_REQUEST_METHOD_NAME))] #[serde(rename_all = "camelCase")] @@ -23,7 +25,7 @@ pub struct CancelRequestNotification { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } diff --git a/src/rpc.rs b/src/rpc.rs index 48c0e984..c7552689 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -4,6 +4,7 @@ use derive_more::{Display, From}; use schemars::JsonSchema; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::value::RawValue; +use serde_with::skip_serializing_none; use crate::{ AGENT_METHOD_NAMES, AgentNotification, AgentRequest, AgentResponse, CLIENT_METHOD_NAMES, @@ -52,10 +53,10 @@ pub enum RequestId { reason = "This comes from the JSON-RPC specification itself" )] #[schemars(rename = "{Params}", extend("x-docs-ignore" = true))] +#[skip_serializing_none] pub struct Request { pub id: RequestId, pub method: Arc, - #[serde(skip_serializing_if = "Option::is_none")] pub params: Option, } @@ -93,9 +94,9 @@ impl Response { reason = "This comes from the JSON-RPC specification itself" )] #[schemars(rename = "{Params}", extend("x-docs-ignore" = true))] +#[skip_serializing_none] pub struct Notification { pub method: Arc, - #[serde(skip_serializing_if = "Option::is_none")] pub params: Option, } diff --git a/src/tool_call.rs b/src/tool_call.rs index 4b96c986..46d927d0 100644 --- a/src/tool_call.rs +++ b/src/tool_call.rs @@ -9,7 +9,7 @@ use std::{path::PathBuf, sync::Arc}; use derive_more::{Display, From}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::{DefaultOnError, VecSkipError, serde_as}; +use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; use crate::{ContentBlock, Error, IntoOption, Meta, TerminalId}; @@ -20,6 +20,7 @@ use crate::{ContentBlock, Error, IntoOption, Meta, TerminalId}; /// /// See protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/tool-calls) #[serde_as] +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -45,17 +46,15 @@ pub struct ToolCall { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub locations: Vec, /// Raw input parameters sent to the tool. - #[serde(skip_serializing_if = "Option::is_none")] pub raw_input: Option, /// Raw output returned by the tool. - #[serde(skip_serializing_if = "Option::is_none")] pub raw_output: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -163,6 +162,7 @@ impl ToolCall { /// the tool call ID are optional - only changed fields need to be included. /// /// See protocol docs: [Updating](https://agentclientprotocol.com/protocol/tool-calls#updating) +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -177,7 +177,7 @@ pub struct ToolCallUpdate { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -210,34 +210,32 @@ impl ToolCallUpdate { /// /// See protocol docs: [Updating](https://agentclientprotocol.com/protocol/tool-calls#updating) #[serde_as] +#[skip_serializing_none] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ToolCallUpdateFields { /// Update the tool kind. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub kind: Option, /// Update the execution status. #[serde_as(deserialize_as = "DefaultOnError")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub status: Option, /// Update the human-readable title. - #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, /// Replace the content collection. #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub content: Option>, /// Replace the locations collection. #[serde_as(deserialize_as = "Option>")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub locations: Option>, /// Update the raw input. - #[serde(skip_serializing_if = "Option::is_none")] pub raw_input: Option, /// Update the raw output. - #[serde(skip_serializing_if = "Option::is_none")] pub raw_output: Option, } @@ -488,6 +486,7 @@ impl From for ToolCallContent { } /// Standard content block (text, images, resources). +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -499,7 +498,7 @@ pub struct Content { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -529,6 +528,7 @@ impl Content { /// The terminal must be added before calling `terminal/release`. /// /// See protocol docs: [Terminal](https://agentclientprotocol.com/protocol/terminals) +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -539,7 +539,7 @@ pub struct Terminal { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -569,6 +569,7 @@ impl Terminal { /// Shows changes to files in a format suitable for display in the client UI. /// /// See protocol docs: [Content](https://agentclientprotocol.com/protocol/tool-calls#content) +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -584,7 +585,7 @@ pub struct Diff { /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } @@ -624,6 +625,7 @@ impl Diff { /// which files the agent is working with in real-time. /// /// See protocol docs: [Following the Agent](https://agentclientprotocol.com/protocol/tool-calls#following-the-agent) +#[skip_serializing_none] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -631,14 +633,14 @@ pub struct ToolCallLocation { /// The file path being accessed or modified. pub path: PathBuf, /// Optional line number within the file. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub line: Option, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. /// /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + #[serde(rename = "_meta")] pub meta: Option, } From 1a873bbe1108f5d02decd16352ece17e6434fc4c Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 17 Apr 2026 17:10:06 +0200 Subject: [PATCH 3/4] Add optional tracing for skipped list entries Introduce a `tracing` feature that logs `VecSkipError` drops via a shared `SkipListener` inspector, while remaining a zero-cost no-op when the feature is disabled. Also add tests covering inspector callbacks for malformed entries. --- Cargo.lock | 23 +++++++++++++++ Cargo.toml | 6 ++++ src/agent.rs | 24 +++++++-------- src/client.rs | 8 ++--- src/content.rs | 4 +-- src/nes.rs | 18 ++++++------ src/plan.rs | 4 +-- src/serde_util.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++ src/tool_call.rs | 10 +++---- 9 files changed, 138 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 891c586c..0870874b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,7 @@ dependencies = [ "serde_json", "serde_with", "strum", + "tracing", ] [[package]] @@ -308,6 +309,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "powerfmt" version = "0.2.0" @@ -570,6 +577,22 @@ dependencies = [ "time-core", ] +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/Cargo.toml b/Cargo.toml index 0f31c612..88cb5099 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,11 @@ unstable_session_usage = [] unstable_message_id = [] unstable_boolean_config = [] +# Emit `tracing::warn!` events when `VecSkipError` drops a malformed list +# entry during deserialization. When disabled (the default), the inspector +# hook compiles down to serde_with's built-in no-op and has zero runtime cost. +tracing = ["dep:tracing"] + [[bin]] name = "generate" path = "src/bin/generate.rs" @@ -57,6 +62,7 @@ serde = { version = "1", features = ["derive", "rc"] } serde_json = { version = "1", features = ["raw_value"] } serde_with = { version = "3.18.0", features = ["json", "schemars_1"] } strum = { version = "0.28", features = ["derive"] } +tracing = { version = "0.1", default-features = false, optional = true } [lints.rust] future_incompatible = { level = "warn", priority = -1 } diff --git a/src/agent.rs b/src/agent.rs index f212be46..11a8584d 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -17,7 +17,7 @@ use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; use crate::RequiredNullable; use crate::{ ClientCapabilities, ContentBlock, ExtNotification, ExtRequest, ExtResponse, IntoOption, Meta, - ProtocolVersion, SessionId, + ProtocolVersion, SessionId, SkipListener, }; #[cfg(feature = "unstable_nes")] @@ -128,7 +128,7 @@ pub struct InitializeResponse { #[serde(default)] pub agent_capabilities: AgentCapabilities, /// Authentication methods supported by the agent. - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] #[serde(default)] pub auth_methods: Vec, /// Information about the Agent name and version sent to the Client. @@ -1024,7 +1024,7 @@ pub struct NewSessionResponse { #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1200,7 +1200,7 @@ pub struct LoadSessionResponse { #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1384,7 +1384,7 @@ pub struct ForkSessionResponse { #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1574,7 +1574,7 @@ pub struct ResumeSessionResponse { #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1817,7 +1817,7 @@ impl ListSessionsRequest { #[non_exhaustive] pub struct ListSessionsResponse { /// Array of session information objects - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] pub sessions: Vec, /// Opaque cursor token. If present, pass this in the next request's cursor parameter /// to fetch the next page. If absent, there are no more results. @@ -1962,7 +1962,7 @@ pub struct SessionModeState { /// The current mode the Agent is in. pub current_mode_id: SessionModeId, /// The set of modes that the Agent can operate in - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] pub available_modes: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -2663,7 +2663,7 @@ impl SetSessionConfigOptionRequest { #[non_exhaustive] pub struct SetSessionConfigOptionResponse { /// The full set of configuration options and their current values. - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] pub config_options: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -3255,7 +3255,7 @@ pub struct SessionModelState { /// The current model the Agent is in. pub current_model_id: ModelId, /// The set of models that the Agent can use - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] pub available_models: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -3531,7 +3531,7 @@ pub struct ProviderInfo { /// Provider identifier, for example "main" or "openai". pub id: String, /// Supported protocol types for this provider. - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] pub supported: Vec, /// Whether this provider is mandatory and cannot be disabled via `providers/disable`. /// If true, clients must not call `providers/disable` for this id. @@ -3632,7 +3632,7 @@ impl ListProvidersRequest { #[non_exhaustive] pub struct ListProvidersResponse { /// Configurable providers with current routing info suitable for UI display. - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] pub providers: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at diff --git a/src/client.rs b/src/client.rs index 4ba85b13..c4d4d743 100644 --- a/src/client.rs +++ b/src/client.rs @@ -17,7 +17,7 @@ use crate::elicitation::{ }; use crate::{ ContentBlock, ExtNotification, ExtRequest, ExtResponse, IntoOption, Meta, Plan, - SessionConfigOption, SessionId, SessionModeId, ToolCall, ToolCallUpdate, + SessionConfigOption, SessionId, SessionModeId, SkipListener, ToolCall, ToolCallUpdate, }; use crate::{IntoMaybeUndefined, MaybeUndefined}; @@ -162,7 +162,7 @@ impl CurrentModeUpdate { #[non_exhaustive] pub struct ConfigOptionUpdate { /// The full set of configuration options and their current values. - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] pub config_options: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -412,7 +412,7 @@ impl ContentChunk { #[non_exhaustive] pub struct AvailableCommandsUpdate { /// Commands the agent can execute - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] pub available_commands: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -1554,7 +1554,7 @@ pub struct ClientCapabilities { /// /// The position encodings supported by the client, in order of preference. #[cfg(feature = "unstable_nes")] - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub position_encodings: Vec, diff --git a/src/content.rs b/src/content.rs index f511962f..cb2d5f10 100644 --- a/src/content.rs +++ b/src/content.rs @@ -13,7 +13,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; -use crate::{IntoOption, Meta}; +use crate::{IntoOption, Meta, SkipListener}; /// Content blocks represent displayable information in the Agent Client Protocol. /// @@ -459,7 +459,7 @@ impl ResourceLink { #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Annotations { - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub audience: Option>, pub last_modified: Option, diff --git a/src/nes.rs b/src/nes.rs index 24ce9f07..0f51b56b 100644 --- a/src/nes.rs +++ b/src/nes.rs @@ -8,7 +8,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; -use crate::{IntoOption, Meta, SessionId}; +use crate::{IntoOption, Meta, SessionId, SkipListener}; // Method name constants @@ -1099,7 +1099,7 @@ pub struct StartNesRequest { /// The root URI of the workspace. pub workspace_uri: Option, /// The workspace folders. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub workspace_folders: Option>, /// Repository metadata, if the workspace is a git repository. @@ -1436,27 +1436,27 @@ impl SuggestNesRequest { #[non_exhaustive] pub struct NesSuggestContext { /// Recently accessed files. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub recent_files: Option>, /// Related code snippets. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub related_snippets: Option>, /// Recent edit history. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub edit_history: Option>, /// Recent user actions (typing, navigation, etc.). - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub user_actions: Option>, /// Currently open files in the editor. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub open_files: Option>, /// Current diagnostics (errors, warnings). - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub diagnostics: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1757,7 +1757,7 @@ pub enum NesDiagnosticSeverity { #[non_exhaustive] pub struct SuggestNesResponse { /// The list of suggestions. - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] pub suggestions: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at diff --git a/src/plan.rs b/src/plan.rs index 7ca804b8..52722a5c 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -9,7 +9,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::{VecSkipError, serde_as, skip_serializing_none}; -use crate::{IntoOption, Meta}; +use crate::{IntoOption, Meta, SkipListener}; /// An execution plan for accomplishing complex tasks. /// @@ -28,7 +28,7 @@ pub struct Plan { /// /// When updating a plan, the agent must send a complete list of all entries /// with their current status. The client replaces the entire plan with each update. - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] pub entries: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at diff --git a/src/serde_util.rs b/src/serde_util.rs index 3deb4fdc..60adc277 100644 --- a/src/serde_util.rs +++ b/src/serde_util.rs @@ -4,6 +4,8 @@ //! //! - [`MaybeUndefined`] — three-state: undefined (key absent), null, or value. //! - [`RequiredNullable`] — required-but-nullable: key must be present, value may be null. +//! - [`SkipListener`] — [`serde_with::InspectError`] hook used by every +//! `VecSkipError` call site in the protocol types. //! //! ## Builder traits //! @@ -22,6 +24,79 @@ use std::{ use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +// ---- SkipListener ---- + +/// Inspector passed to every `VecSkipError<_, SkipListener>` in the protocol +/// types so that malformed list entries dropped during deserialization are +/// surfaced to observability tooling rather than vanishing silently. +/// +/// - With the `tracing` feature enabled, this is a zero-sized type whose +/// [`InspectError`](serde_with::InspectError) implementation emits a +/// [`tracing::warn!`] event on every skipped entry. +/// - With the feature disabled (the default), it resolves to `()` — which +/// `serde_with` ships with a no-op `InspectError` implementation — so call +/// sites incur zero runtime cost. +#[cfg(feature = "tracing")] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub struct SkipListener; + +#[cfg(feature = "tracing")] +impl serde_with::InspectError for SkipListener { + fn inspect_error(error: impl serde::de::Error) { + tracing::warn!( + %error, + "skipped malformed list entry during deserialization", + ); + } +} + +/// Zero-cost stand-in for [`SkipListener`] when the `tracing` feature is +/// disabled. Resolves to `()`, which `serde_with` already ships with a no-op +/// `InspectError` implementation. +#[cfg(not(feature = "tracing"))] +pub type SkipListener = (); + +#[cfg(test)] +mod skip_listener_tests { + use std::cell::Cell; + + use serde::{Deserialize, Serialize}; + use serde_json::json; + use serde_with::{VecSkipError, serde_as}; + + thread_local! { + static SKIP_COUNT: Cell = const { Cell::new(0) }; + } + + /// Test-only inspector that counts skipped entries. + struct CountingListener; + + impl serde_with::InspectError for CountingListener { + fn inspect_error(_error: impl serde::de::Error) { + SKIP_COUNT.with(|c| c.set(c.get() + 1)); + } + } + + #[serde_as] + #[derive(Serialize, Deserialize, Debug, PartialEq)] + struct Wrapper { + #[serde_as(deserialize_as = "VecSkipError<_, CountingListener>")] + values: Vec, + } + + #[test] + fn inspector_runs_for_each_skipped_entry() { + SKIP_COUNT.with(|c| c.set(0)); + + let input = json!({"values": [1, "oops", 2, {}, 3]}); + let wrapper: Wrapper = serde_json::from_value(input).unwrap(); + + assert_eq!(wrapper.values, vec![1, 2, 3]); + assert_eq!(SKIP_COUNT.with(Cell::get), 2); + } +} + // ---- IntoOption ---- /// Utility trait for builder methods for optional values. diff --git a/src/tool_call.rs b/src/tool_call.rs index 46d927d0..6e220d46 100644 --- a/src/tool_call.rs +++ b/src/tool_call.rs @@ -11,7 +11,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; -use crate::{ContentBlock, Error, IntoOption, Meta, TerminalId}; +use crate::{ContentBlock, Error, IntoOption, Meta, SkipListener, TerminalId}; /// Represents a tool call that the language model has requested. /// @@ -37,12 +37,12 @@ pub struct ToolCall { #[serde(default, skip_serializing_if = "ToolCallStatus::is_default")] pub status: ToolCallStatus, /// Content produced by the tool call. - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub content: Vec, /// File locations affected by this tool call. /// Enables "follow-along" features in clients. - #[serde_as(deserialize_as = "VecSkipError<_>")] + #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub locations: Vec, /// Raw input parameters sent to the tool. @@ -226,11 +226,11 @@ pub struct ToolCallUpdateFields { /// Update the human-readable title. pub title: Option, /// Replace the content collection. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub content: Option>, /// Replace the locations collection. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "Option>")] #[serde(default)] pub locations: Option>, /// Update the raw input. From 09e971d2d02eb118d8b65b3be255e27ced6efc9f Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 17 Apr 2026 18:52:21 +0200 Subject: [PATCH 4/4] Make List Deserialization Fall Back to Defaults Wrap list deserializers with `DefaultOnError` so invalid outer shapes default instead of failing, while still skipping invalid elements inside valid arrays. --- src/agent.rs | 22 +++++------ src/client.rs | 6 +-- src/content.rs | 2 +- src/nes.rs | 16 ++++---- src/plan.rs | 4 +- src/serde_util.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++- src/tool_call.rs | 8 ++-- 7 files changed, 127 insertions(+), 30 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 11a8584d..9caf0744 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -128,7 +128,7 @@ pub struct InitializeResponse { #[serde(default)] pub agent_capabilities: AgentCapabilities, /// Authentication methods supported by the agent. - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] #[serde(default)] pub auth_methods: Vec, /// Information about the Agent name and version sent to the Client. @@ -1024,7 +1024,7 @@ pub struct NewSessionResponse { #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1200,7 +1200,7 @@ pub struct LoadSessionResponse { #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1384,7 +1384,7 @@ pub struct ForkSessionResponse { #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1574,7 +1574,7 @@ pub struct ResumeSessionResponse { #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub config_options: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1817,7 +1817,7 @@ impl ListSessionsRequest { #[non_exhaustive] pub struct ListSessionsResponse { /// Array of session information objects - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] pub sessions: Vec, /// Opaque cursor token. If present, pass this in the next request's cursor parameter /// to fetch the next page. If absent, there are no more results. @@ -1962,7 +1962,7 @@ pub struct SessionModeState { /// The current mode the Agent is in. pub current_mode_id: SessionModeId, /// The set of modes that the Agent can operate in - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] pub available_modes: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -2663,7 +2663,7 @@ impl SetSessionConfigOptionRequest { #[non_exhaustive] pub struct SetSessionConfigOptionResponse { /// The full set of configuration options and their current values. - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] pub config_options: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -3255,7 +3255,7 @@ pub struct SessionModelState { /// The current model the Agent is in. pub current_model_id: ModelId, /// The set of models that the Agent can use - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] pub available_models: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -3531,7 +3531,7 @@ pub struct ProviderInfo { /// Provider identifier, for example "main" or "openai". pub id: String, /// Supported protocol types for this provider. - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] pub supported: Vec, /// Whether this provider is mandatory and cannot be disabled via `providers/disable`. /// If true, clients must not call `providers/disable` for this id. @@ -3632,7 +3632,7 @@ impl ListProvidersRequest { #[non_exhaustive] pub struct ListProvidersResponse { /// Configurable providers with current routing info suitable for UI display. - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] pub providers: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at diff --git a/src/client.rs b/src/client.rs index c4d4d743..2915ea59 100644 --- a/src/client.rs +++ b/src/client.rs @@ -162,7 +162,7 @@ impl CurrentModeUpdate { #[non_exhaustive] pub struct ConfigOptionUpdate { /// The full set of configuration options and their current values. - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] pub config_options: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -412,7 +412,7 @@ impl ContentChunk { #[non_exhaustive] pub struct AvailableCommandsUpdate { /// Commands the agent can execute - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] pub available_commands: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -1554,7 +1554,7 @@ pub struct ClientCapabilities { /// /// The position encodings supported by the client, in order of preference. #[cfg(feature = "unstable_nes")] - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub position_encodings: Vec, diff --git a/src/content.rs b/src/content.rs index cb2d5f10..0f301bd8 100644 --- a/src/content.rs +++ b/src/content.rs @@ -459,7 +459,7 @@ impl ResourceLink { #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Annotations { - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub audience: Option>, pub last_modified: Option, diff --git a/src/nes.rs b/src/nes.rs index 0f51b56b..0e5730e1 100644 --- a/src/nes.rs +++ b/src/nes.rs @@ -1099,7 +1099,7 @@ pub struct StartNesRequest { /// The root URI of the workspace. pub workspace_uri: Option, /// The workspace folders. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub workspace_folders: Option>, /// Repository metadata, if the workspace is a git repository. @@ -1436,27 +1436,27 @@ impl SuggestNesRequest { #[non_exhaustive] pub struct NesSuggestContext { /// Recently accessed files. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub recent_files: Option>, /// Related code snippets. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub related_snippets: Option>, /// Recent edit history. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub edit_history: Option>, /// Recent user actions (typing, navigation, etc.). - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub user_actions: Option>, /// Currently open files in the editor. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub open_files: Option>, /// Current diagnostics (errors, warnings). - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub diagnostics: Option>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -1757,7 +1757,7 @@ pub enum NesDiagnosticSeverity { #[non_exhaustive] pub struct SuggestNesResponse { /// The list of suggestions. - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] pub suggestions: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at diff --git a/src/plan.rs b/src/plan.rs index 52722a5c..a4910c13 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -7,7 +7,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::{VecSkipError, serde_as, skip_serializing_none}; +use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; use crate::{IntoOption, Meta, SkipListener}; @@ -28,7 +28,7 @@ pub struct Plan { /// /// When updating a plan, the agent must send a complete list of all entries /// with their current status. The client replaces the entire plan with each update. - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] pub entries: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at diff --git a/src/serde_util.rs b/src/serde_util.rs index 60adc277..952ae4ce 100644 --- a/src/serde_util.rs +++ b/src/serde_util.rs @@ -63,7 +63,7 @@ mod skip_listener_tests { use serde::{Deserialize, Serialize}; use serde_json::json; - use serde_with::{VecSkipError, serde_as}; + use serde_with::{DefaultOnError, VecSkipError, serde_as}; thread_local! { static SKIP_COUNT: Cell = const { Cell::new(0) }; @@ -95,6 +95,103 @@ mod skip_listener_tests { assert_eq!(wrapper.values, vec![1, 2, 3]); assert_eq!(SKIP_COUNT.with(Cell::get), 2); } + + /// Mirrors the pattern applied to every required `Vec` field in the + /// protocol: `DefaultOnError>` + `#[serde(default)]`. + /// Element-level failures are skipped; any outer shape error (`null`, a + /// string, a map, etc.) collapses to `Default::default()` (i.e. `vec![]`). + #[serde_as] + #[derive(Deserialize, Debug, PartialEq)] + struct ResilientVec { + #[serde_as(deserialize_as = "DefaultOnError>")] + #[serde(default)] + values: Vec, + } + + #[test] + fn resilient_vec_tolerates_missing_null_and_wrong_type() { + // Missing field -> `#[serde(default)]` supplies `vec![]`. + let r: ResilientVec = serde_json::from_value(json!({})).unwrap(); + assert_eq!(r.values, Vec::::new()); + + // Explicit null -> `DefaultOnError` swallows the type error. + let r: ResilientVec = serde_json::from_value(json!({"values": null})).unwrap(); + assert_eq!(r.values, Vec::::new()); + + // Wrong outer type (string) -> `DefaultOnError` swallows. + let r: ResilientVec = serde_json::from_value(json!({"values": "oops"})).unwrap(); + assert_eq!(r.values, Vec::::new()); + + // Wrong outer type (object) -> `DefaultOnError` swallows. + let r: ResilientVec = serde_json::from_value(json!({"values": {"k": 1}})).unwrap(); + assert_eq!(r.values, Vec::::new()); + + // Valid array with element errors -> `VecSkipError` skips per-element. + SKIP_COUNT.with(|c| c.set(0)); + let r: ResilientVec = + serde_json::from_value(json!({"values": [1, "oops", 2, {}, 3]})).unwrap(); + assert_eq!(r.values, vec![1, 2, 3]); + assert_eq!(SKIP_COUNT.with(Cell::get), 2); + } + + #[test] + fn resilient_vec_does_not_invoke_inspector_on_outer_failure() { + SKIP_COUNT.with(|c| c.set(0)); + + // Outer failures are swallowed silently by `DefaultOnError`; the + // inspector only sees per-element failures inside a valid array. + let _r: ResilientVec = serde_json::from_value(json!({"values": null})).unwrap(); + let _r: ResilientVec = serde_json::from_value(json!({"values": "oops"})).unwrap(); + let _r: ResilientVec = serde_json::from_value(json!({"values": {}})).unwrap(); + + assert_eq!(SKIP_COUNT.with(Cell::get), 0); + } + + /// Mirrors the pattern applied to every optional `Option>` field: + /// `DefaultOnError>>` + `#[serde(default)]`. + /// `null` becomes `None`; outer shape errors also collapse to `None`; + /// element-level failures are skipped inside the array. + #[serde_as] + #[derive(Deserialize, Debug, PartialEq)] + struct ResilientOptionVec { + #[serde_as(deserialize_as = "DefaultOnError>>")] + #[serde(default)] + values: Option>, + } + + #[test] + fn resilient_option_vec_tolerates_missing_null_and_wrong_type() { + // Missing field -> `None`. + let r: ResilientOptionVec = serde_json::from_value(json!({})).unwrap(); + assert_eq!(r.values, None); + + // Explicit null -> `None`. + let r: ResilientOptionVec = serde_json::from_value(json!({"values": null})).unwrap(); + assert_eq!(r.values, None); + + // Empty array -> `Some(vec![])`. + let r: ResilientOptionVec = serde_json::from_value(json!({"values": []})).unwrap(); + assert_eq!(r.values, Some(Vec::::new())); + + // Valid array -> `Some(vec)`. + let r: ResilientOptionVec = serde_json::from_value(json!({"values": [1, 2, 3]})).unwrap(); + assert_eq!(r.values, Some(vec![1, 2, 3])); + + // Wrong outer type (string) -> `DefaultOnError` collapses to `None`. + let r: ResilientOptionVec = serde_json::from_value(json!({"values": "oops"})).unwrap(); + assert_eq!(r.values, None); + + // Wrong outer type (object) -> `DefaultOnError` collapses to `None`. + let r: ResilientOptionVec = serde_json::from_value(json!({"values": {"k": 1}})).unwrap(); + assert_eq!(r.values, None); + + // Valid array with element errors -> `VecSkipError` skips per-element. + SKIP_COUNT.with(|c| c.set(0)); + let r: ResilientOptionVec = + serde_json::from_value(json!({"values": [1, "oops", 2, {}, 3]})).unwrap(); + assert_eq!(r.values, Some(vec![1, 2, 3])); + assert_eq!(SKIP_COUNT.with(Cell::get), 2); + } } // ---- IntoOption ---- diff --git a/src/tool_call.rs b/src/tool_call.rs index 6e220d46..f096982d 100644 --- a/src/tool_call.rs +++ b/src/tool_call.rs @@ -37,12 +37,12 @@ pub struct ToolCall { #[serde(default, skip_serializing_if = "ToolCallStatus::is_default")] pub status: ToolCallStatus, /// Content produced by the tool call. - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub content: Vec, /// File locations affected by this tool call. /// Enables "follow-along" features in clients. - #[serde_as(deserialize_as = "VecSkipError<_, SkipListener>")] + #[serde_as(deserialize_as = "DefaultOnError>")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub locations: Vec, /// Raw input parameters sent to the tool. @@ -226,11 +226,11 @@ pub struct ToolCallUpdateFields { /// Update the human-readable title. pub title: Option, /// Replace the content collection. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub content: Option>, /// Replace the locations collection. - #[serde_as(deserialize_as = "Option>")] + #[serde_as(deserialize_as = "DefaultOnError>>")] #[serde(default)] pub locations: Option>, /// Update the raw input.