diff --git a/Cargo.lock b/Cargo.lock index 524c4c07..0870874b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,10 +8,21 @@ version = "0.11.7" dependencies = [ "anyhow", "derive_more", - "schemars", + "schemars 1.2.1", "serde", "serde_json", + "serde_with", "strum", + "tracing", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", ] [[package]] @@ -20,6 +31,52 @@ 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 +86,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 +165,162 @@ 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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -123,6 +368,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 +471,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 +546,53 @@ 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 = "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" @@ -258,6 +611,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..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" @@ -55,7 +60,9 @@ 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"] } +tracing = { version = "0.1", default-features = false, optional = true } [lints.rust] future_incompatible = { level = "warn", priority = -1 } 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/src/agent.rs b/src/agent.rs index 0dbfb838..9caf0744 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -11,12 +11,13 @@ use std::collections::HashMap; use derive_more::{Display, From}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; #[cfg(feature = "unstable_llm_providers")] use crate::RequiredNullable; use crate::{ ClientCapabilities, ContentBlock, ExtNotification, ExtRequest, ExtResponse, IntoOption, Meta, - ProtocolVersion, SessionId, + ProtocolVersion, SessionId, SkipListener, }; #[cfg(feature = "unstable_nes")] @@ -42,6 +43,8 @@ use crate::nes::{ /// Sent by the client to establish connection and negotiate capabilities. /// /// 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")] @@ -55,14 +58,15 @@ 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)] 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, } @@ -108,6 +112,8 @@ impl InitializeRequest { /// Contains the negotiated protocol version and agent capabilities. /// /// 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")] @@ -122,19 +128,21 @@ pub struct InitializeResponse { #[serde(default)] pub agent_capabilities: AgentCapabilities, /// Authentication methods supported by the agent. + #[serde_as(deserialize_as = "DefaultOnError>")] #[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)] 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, } @@ -186,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] @@ -206,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, } @@ -248,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")] @@ -261,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, } @@ -287,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")] @@ -297,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, } @@ -329,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")] @@ -339,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, } @@ -368,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")] @@ -378,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, } @@ -407,6 +420,8 @@ 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] @@ -414,14 +429,15 @@ 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)] 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, } @@ -459,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 { @@ -467,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, } @@ -589,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] @@ -598,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, } @@ -647,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] @@ -656,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, } @@ -722,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] @@ -729,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. @@ -749,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, } @@ -826,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] @@ -835,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")] @@ -848,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, } @@ -904,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")] @@ -930,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, } @@ -980,6 +997,8 @@ 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] +#[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")] @@ -992,7 +1011,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)] pub modes: Option, /// **UNSTABLE** /// @@ -1000,17 +1020,19 @@ 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)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde(skip_serializing_if = "Option::is_none")] + #[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 /// 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, } @@ -1077,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")] @@ -1105,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, } @@ -1154,6 +1177,8 @@ 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")] @@ -1162,7 +1187,8 @@ pub struct LoadSessionResponse { /// Initial mode state if supported by the Agent /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub modes: Option, /// **UNSTABLE** /// @@ -1170,17 +1196,19 @@ pub struct LoadSessionResponse { /// /// Initial model state if supported by the Agent #[cfg(feature = "unstable_session_model")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[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 /// 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, } @@ -1246,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")] @@ -1275,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, } @@ -1330,6 +1359,8 @@ 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")] @@ -1340,7 +1371,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)] pub modes: Option, /// **UNSTABLE** /// @@ -1348,17 +1380,19 @@ 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)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde(skip_serializing_if = "Option::is_none")] + #[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 /// 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, } @@ -1432,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")] @@ -1461,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, } @@ -1516,6 +1551,8 @@ 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")] @@ -1524,7 +1561,8 @@ pub struct ResumeSessionResponse { /// Initial mode state if supported by the Agent /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub modes: Option, /// **UNSTABLE** /// @@ -1532,17 +1570,19 @@ pub struct ResumeSessionResponse { /// /// Initial model state if supported by the Agent #[cfg(feature = "unstable_session_model")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub models: Option, /// Initial session configuration options if supported by the Agent. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[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 /// 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, } @@ -1610,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")] @@ -1622,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, } @@ -1654,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")] @@ -1664,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, } @@ -1692,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** /// @@ -1712,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, } @@ -1768,23 +1809,25 @@ 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")] #[non_exhaustive] pub struct ListSessionsResponse { /// Array of session information objects + #[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. - #[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, } @@ -1817,6 +1860,8 @@ 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] @@ -1837,17 +1882,19 @@ 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)] pub title: Option, /// ISO 8601 timestamp of last activity - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[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, } @@ -1906,6 +1953,8 @@ impl SessionInfo { // Session modes /// 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] @@ -1913,13 +1962,14 @@ 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 = "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 /// 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, } @@ -1951,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, } @@ -2012,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")] @@ -2026,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, } @@ -2048,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")] @@ -2058,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, } @@ -2125,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] @@ -2134,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, } @@ -2175,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] @@ -2190,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, } @@ -2332,6 +2387,8 @@ 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] @@ -2341,10 +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(default, skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub category: Option, /// Type-specific fields for this configuration option. #[serde(flatten)] @@ -2354,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, } @@ -2524,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")] @@ -2548,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, } @@ -2596,19 +2655,22 @@ 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")] #[non_exhaustive] pub struct SetSessionConfigOptionResponse { /// The full set of configuration options and their current values. + #[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 /// 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, } @@ -2661,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] @@ -2676,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, } @@ -2711,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] @@ -2726,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, } @@ -2761,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] @@ -2778,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, } @@ -2821,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] @@ -2834,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, } @@ -2861,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] @@ -2874,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, } @@ -2907,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")] @@ -2924,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. /// @@ -2945,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, } @@ -2992,6 +3059,8 @@ 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] +#[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")] @@ -3007,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, @@ -3017,14 +3085,15 @@ 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)] 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, } @@ -3114,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] @@ -3125,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, } @@ -3179,6 +3246,8 @@ 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] @@ -3186,13 +3255,14 @@ 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 = "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 /// 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, } @@ -3245,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] @@ -3254,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, } @@ -3302,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")] @@ -3316,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, } @@ -3349,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")] @@ -3359,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, } @@ -3449,6 +3522,8 @@ 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] @@ -3456,6 +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 = "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. @@ -3468,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, } @@ -3508,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")] @@ -3518,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, } @@ -3547,19 +3624,22 @@ 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")] #[non_exhaustive] pub struct ListProvidersResponse { /// Configurable providers with current routing info suitable for UI display. + #[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 /// 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, } @@ -3593,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")] @@ -3613,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, } @@ -3656,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")] @@ -3666,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, } @@ -3695,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")] @@ -3707,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, } @@ -3739,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")] @@ -3749,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, } @@ -3780,6 +3864,8 @@ impl DisableProvidersResponse { /// available features and content types. /// /// 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] @@ -3811,7 +3897,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)] pub providers: Option, /// **UNSTABLE** /// @@ -3819,7 +3906,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)] pub nes: Option, /// **UNSTABLE** /// @@ -3827,14 +3915,15 @@ 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)] 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, } @@ -3941,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 { @@ -3949,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, } @@ -3981,12 +4071,15 @@ 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] +#[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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub list: Option, /// **UNSTABLE** /// @@ -3994,7 +4087,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)] pub additional_directories: Option, /// **UNSTABLE** /// @@ -4002,7 +4096,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)] pub fork: Option, /// **UNSTABLE** /// @@ -4010,7 +4105,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)] pub resume: Option, /// **UNSTABLE** /// @@ -4018,14 +4114,15 @@ 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)] 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, } @@ -4096,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 { @@ -4104,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, } @@ -4135,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 { @@ -4143,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, } @@ -4174,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 { @@ -4182,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, } @@ -4213,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 { @@ -4221,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, } @@ -4252,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 { @@ -4260,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, } @@ -4295,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] @@ -4316,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, } @@ -4363,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] @@ -4378,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, } @@ -4967,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")] @@ -4979,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 652cf4e5..2915ea59 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, skip_serializing_none}; #[cfg(feature = "unstable_elicitation")] use crate::elicitation::{ @@ -16,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}; @@ -30,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")] @@ -44,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, } @@ -115,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] @@ -126,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, } @@ -152,18 +155,21 @@ 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] pub struct ConfigOptionUpdate { /// The full set of configuration options and their current values. + #[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 /// 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 +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] @@ -207,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, } @@ -249,6 +256,8 @@ 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] @@ -258,14 +267,15 @@ 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)] 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, } @@ -328,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] @@ -344,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, } @@ -395,18 +405,21 @@ 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] pub struct AvailableCommandsUpdate { /// Commands the agent can execute + #[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 /// 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, } @@ -432,6 +445,8 @@ 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] @@ -441,13 +456,15 @@ 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 /// 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, } @@ -491,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] @@ -502,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, } @@ -534,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")] @@ -550,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, } @@ -582,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] @@ -597,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, } @@ -660,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")] @@ -673,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, } @@ -718,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] @@ -729,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, } @@ -759,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")] @@ -775,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, } @@ -807,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))] @@ -817,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, } @@ -844,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")] @@ -854,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, } @@ -907,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")] @@ -918,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, } @@ -959,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))] @@ -975,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. /// @@ -985,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, } @@ -1058,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))] @@ -1070,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, } @@ -1096,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))] @@ -1110,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, } @@ -1137,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))] @@ -1153,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, } @@ -1188,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))] @@ -1202,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, } @@ -1229,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))] @@ -1239,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, } @@ -1262,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))] @@ -1276,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, } @@ -1303,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))] @@ -1313,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, } @@ -1336,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))] @@ -1350,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, } @@ -1377,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))] @@ -1390,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, } @@ -1416,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] @@ -1429,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, } @@ -1473,6 +1506,8 @@ impl TerminalExitStatus { /// available features and methods. /// /// 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] @@ -1501,7 +1536,8 @@ pub struct ClientCapabilities { /// Elicitation capabilities supported by the client. /// Determines which elicitation modes the agent may use. #[cfg(feature = "unstable_elicitation")] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub elicitation: Option, /// **UNSTABLE** /// @@ -1509,7 +1545,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)] pub nes: Option, /// **UNSTABLE** /// @@ -1517,6 +1554,7 @@ pub struct ClientCapabilities { /// /// The position encodings supported by the client, in order of preference. #[cfg(feature = "unstable_nes")] + #[serde_as(deserialize_as = "DefaultOnError>")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub position_encodings: Vec, @@ -1525,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, } @@ -1619,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] @@ -1633,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, } @@ -1669,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] @@ -1684,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 c25539f6..0f301bd8 100644 --- a/src/content.rs +++ b/src/content.rs @@ -11,8 +11,9 @@ 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. /// @@ -59,10 +60,13 @@ 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub annotations: Option, pub text: String, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -70,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, } @@ -109,22 +113,24 @@ 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[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, } @@ -165,11 +171,14 @@ 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub annotations: Option, pub data: String, pub mime_type: String, @@ -178,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, } @@ -212,10 +221,13 @@ 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub annotations: Option, pub resource: EmbeddedResourceResource, /// The _meta property is reserved by ACP to allow clients and agents to attach additional @@ -223,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, } @@ -265,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, @@ -278,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, } @@ -312,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 @@ -325,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, } @@ -359,20 +371,19 @@ 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[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 @@ -380,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, } @@ -442,22 +453,23 @@ 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError>>")] + #[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 61dc6c7a..6c8b6180 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, skip_serializing_none}; use crate::client::{ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME}; use crate::tool_call::ToolCallId; @@ -84,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>, } @@ -225,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, } @@ -290,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, } @@ -355,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, } @@ -450,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>, } @@ -599,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] @@ -607,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, } @@ -740,22 +719,26 @@ 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] +#[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(default, skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub form: Option, /// Whether the client supports URL-based elicitation. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[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, } @@ -796,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] @@ -805,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, } @@ -832,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] @@ -841,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, } @@ -888,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] @@ -895,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, } @@ -959,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")] @@ -974,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, } @@ -1110,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")] @@ -1123,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, } @@ -1168,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>, } @@ -1264,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")] @@ -1276,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 58323d22..0e5730e1 100644 --- a/src/nes.rs +++ b/src/nes.rs @@ -6,8 +6,9 @@ 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 @@ -92,22 +93,26 @@ impl Range { // Agent NES capabilities /// 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] 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)] 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, } @@ -142,19 +147,22 @@ 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[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, } @@ -183,31 +191,38 @@ 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] 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)] 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)] 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)] 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)] 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, } @@ -269,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] @@ -278,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, } @@ -290,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] @@ -301,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, } @@ -339,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] @@ -348,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, } @@ -360,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] @@ -369,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, } @@ -381,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] @@ -390,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, } @@ -402,34 +422,42 @@ 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] 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)] 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)] 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)] 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)] pub open_files: Option, /// Whether the agent wants diagnostics context. - #[serde(skip_serializing_if = "Option::is_none")] + #[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 /// 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, } @@ -500,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, } @@ -524,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] @@ -533,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, } @@ -545,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, } @@ -569,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, } @@ -593,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] @@ -602,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, } @@ -614,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] @@ -623,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, } @@ -637,25 +668,30 @@ impl NesDiagnosticsCapabilities { // Client NES capabilities /// 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] 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)] 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)] 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, } @@ -699,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] @@ -708,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, } @@ -720,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] @@ -729,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, } @@ -741,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] @@ -750,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, } @@ -764,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")] @@ -784,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, } @@ -820,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")] @@ -838,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, } @@ -875,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, @@ -905,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")] @@ -919,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, } @@ -946,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")] @@ -960,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, } @@ -987,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")] @@ -1007,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, } @@ -1045,26 +1089,29 @@ impl DidFocusDocumentNotification { // NES session start /// 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError>>")] + #[serde(default)] 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)] 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, } @@ -1168,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")] @@ -1180,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, } @@ -1211,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")] @@ -1223,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, } @@ -1249,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")] @@ -1259,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, } @@ -1299,6 +1349,8 @@ 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")] @@ -1313,19 +1365,21 @@ 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)] 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)] 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, } @@ -1375,34 +1429,42 @@ 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError>>")] + #[serde(default)] pub recent_files: Option>, /// Related code snippets. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError>>")] + #[serde(default)] pub related_snippets: Option>, /// Recent edit history. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError>>")] + #[serde(default)] pub edit_history: Option>, /// Recent user actions (typing, navigation, etc.). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError>>")] + #[serde(default)] pub user_actions: Option>, /// Currently open files in the editor. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError>>")] + #[serde(default)] pub open_files: Option>, /// Current diagnostics (errors, warnings). - #[serde(skip_serializing_if = "Option::is_none")] + #[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 /// 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, } @@ -1590,6 +1652,8 @@ 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] @@ -1599,10 +1663,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)] 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)] pub last_focused_ms: Option, } @@ -1683,19 +1749,22 @@ pub enum NesDiagnosticSeverity { // NES suggest response /// 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")] #[non_exhaustive] pub struct SuggestNesResponse { /// The list of suggestions. + #[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 /// 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, } @@ -1737,6 +1806,8 @@ 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] @@ -1748,7 +1819,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)] pub cursor_position: Option, } @@ -1848,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] @@ -1861,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, } @@ -1892,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")] @@ -1906,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, } @@ -1933,6 +2006,8 @@ 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")] @@ -1943,14 +2018,15 @@ 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)] 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 60d75083..a4910c13 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -7,8 +7,9 @@ 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}; /// An execution plan for accomplishing complex tasks. /// @@ -17,6 +18,8 @@ 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] +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -25,13 +28,14 @@ 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 = "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 /// 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, } @@ -61,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] @@ -77,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 ada204de..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, } @@ -500,7 +501,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/serde_util.rs b/src/serde_util.rs index 3deb4fdc..952ae4ce 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,176 @@ 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::{DefaultOnError, 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); + } + + /// 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 ---- /// Utility trait for builder methods for optional values. diff --git a/src/tool_call.rs b/src/tool_call.rs index 50b736a5..f096982d 100644 --- a/src/tool_call.rs +++ b/src/tool_call.rs @@ -9,8 +9,9 @@ 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, 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. /// @@ -18,6 +19,8 @@ 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] +#[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -34,24 +37,24 @@ pub struct ToolCall { #[serde(default, skip_serializing_if = "ToolCallStatus::is_default")] pub status: ToolCallStatus, /// Content produced by the tool call. + #[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 = "DefaultOnError>")] #[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, } @@ -159,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] @@ -173,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, } @@ -205,30 +209,33 @@ impl ToolCallUpdate { /// Collections (content, locations) are overwritten, not extended. /// /// 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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default)] pub kind: Option, /// Update the execution status. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError")] + #[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(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError>>")] + #[serde(default)] pub content: Option>, /// Replace the locations collection. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(deserialize_as = "DefaultOnError>>")] + #[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, } @@ -479,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] @@ -490,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, } @@ -520,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] @@ -530,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, } @@ -560,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] @@ -575,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, } @@ -615,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] @@ -622,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, }