diff --git a/AGENTS.md b/AGENTS.md index 8f86b9b57..a57783e21 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,3 +34,63 @@ All paths in the protocol should be absolute - Run `npm run check` to make sure the json and zod schemas gets generated properly Never write readme files related to the conversation unless explicitly asked to. + +## Conventional Commits + +This repository uses **Conventional Commits** for automated releases via release-plz. All commit messages should follow this format: + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +### Commit Types + +- **feat:** A new feature (triggers minor version bump) +- **fix:** A bug fix (triggers patch version bump) +- **docs:** Documentation only changes +- **style:** Code style changes (formatting, missing semicolons, etc.) +- **refactor:** Code changes that neither fix bugs nor add features +- **perf:** Performance improvements +- **test:** Adding or updating tests +- **chore:** Maintenance tasks, dependency updates, etc. +- **ci:** CI/CD configuration changes +- **build:** Build system or external dependency changes + +### Breaking Changes + +Add `!` after the type to indicate breaking changes (triggers major version bump): + +``` +feat!: change API to use async traits +``` + +Or include `BREAKING CHANGE:` in the footer: + +``` +feat: redesign conductor protocol + +BREAKING CHANGE: conductor now requires explicit capability registration +``` + +### Examples + +``` +feat(schema): add support for dynamic proxy chains +fix(unstable): resolve deadlock in message routing +docs(rfd): update README with installation instructions +chore: bump tokio to 1.40 +``` + +### Scope Guidelines + +Common scopes for this repository: + +- `schema` - Anything that touches the actual generated schema +- `unstable` - Any changes that would only touch unstable features +- `unstable-v2` - Any changes that would only touch v2 +- `rust` - Any changes that only touch Rust code and how Rust handles the data with no changes to the schema +- `rfd` - Any changes that only touch RFD documentation diff --git a/agent-client-protocol-schema/Cargo.toml b/agent-client-protocol-schema/Cargo.toml index 235883ea5..e77820a94 100644 --- a/agent-client-protocol-schema/Cargo.toml +++ b/agent-client-protocol-schema/Cargo.toml @@ -36,8 +36,7 @@ unstable = [ ] # Protocol v2 is intentionally NOT part of the `unstable` umbrella. # It introduces a parallel `v2` module and (eventually) a different wire -# version, so it must be opted into explicitly to avoid silently -# changing `ProtocolVersion::LATEST` for `unstable` users. +# version, so it must be opted into explicitly. unstable_protocol_v2 = [] unstable_auth_methods = [] unstable_cancel_request = [] diff --git a/agent-client-protocol-schema/src/v2/conversion.rs b/agent-client-protocol-schema/src/v2/conversion.rs index fc2a1c9cb..fc3d165b9 100644 --- a/agent-client-protocol-schema/src/v2/conversion.rs +++ b/agent-client-protocol-schema/src/v2/conversion.rs @@ -10136,11 +10136,9 @@ mod tests { } #[test] - fn protocol_version_v1_constant_is_unchanged_by_feature_flag() { - // Guards against `LATEST` accidentally being re-pointed to V2 in the - // future; the contract is that `LATEST` is always the latest **stable** - // version, even when the v2 draft feature is enabled. - assert_eq!(ProtocolVersion::LATEST, ProtocolVersion::V1); + fn protocol_version_constants_remain_explicit() { + assert_eq!(ProtocolVersion::V1.as_u16(), 1); + assert_eq!(ProtocolVersion::V2.as_u16(), 2); } /// `?` bubbles a [`ProtocolConversionError`] into a [`v1::Error`] without diff --git a/agent-client-protocol-schema/src/v2/mod.rs b/agent-client-protocol-schema/src/v2/mod.rs index 0ba6c11dc..c8c1af7c8 100644 --- a/agent-client-protocol-schema/src/v2/mod.rs +++ b/agent-client-protocol-schema/src/v2/mod.rs @@ -1,13 +1,12 @@ //! Agent Client Protocol version 2 draft types. //! //! **EXPERIMENTAL.** This module is gated behind the `unstable_protocol_v2` -//! feature, is not part of the [`unstable`] umbrella, and is **not** -//! advertised by [`crate::ProtocolVersion::LATEST`]. The wire format is -//! currently identical to v1 (the default crate-root types) and the types -//! here exist only as a place to evolve v2 without disturbing the stable v1 -//! API. The wire format intentionally diverges from v1 as draft v2 RFDs land. -//! Both the type definitions and the [`conversion`] helpers may change at any -//! time. +//! feature, is not part of the [`unstable`] umbrella, and must be selected +//! explicitly with [`crate::ProtocolVersion::V2`]. The wire format is +//! currently identical to v1 (the default crate-root types) and the types here +//! exist only as a place to evolve v2 without disturbing the stable v1 API. The +//! wire format intentionally diverges from v1 as draft v2 RFDs land. Both the +//! type definitions and the [`conversion`] helpers may change at any time. //! //! [`unstable`]: https://docs.rs/crate/agent-client-protocol-schema/latest/features diff --git a/agent-client-protocol-schema/src/version.rs b/agent-client-protocol-schema/src/version.rs index 9603538a1..f46f6c1a3 100644 --- a/agent-client-protocol-schema/src/version.rs +++ b/agent-client-protocol-schema/src/version.rs @@ -1,13 +1,24 @@ use derive_more::{Display, From}; use schemars::JsonSchema; -use serde::Serialize; +use serde::{Deserialize, Serialize}; /// Protocol version identifier. /// /// This version is only bumped for breaking changes. /// Non-breaking changes should be introduced via capabilities. #[derive( - Debug, Clone, Copy, Serialize, JsonSchema, PartialEq, Eq, PartialOrd, Ord, From, Display, + Debug, + Clone, + Copy, + Serialize, + Deserialize, + JsonSchema, + PartialEq, + Eq, + PartialOrd, + Ord, + From, + Display, )] pub struct ProtocolVersion(u16); @@ -15,8 +26,7 @@ impl ProtocolVersion { /// Version `0` of the protocol. /// /// This was a pre-release version that shouldn't be used in production. - /// It is used as a fallback for any request whose version cannot be parsed - /// as a valid version, and should likely be treated as unsupported. + /// It should likely be treated as unsupported. pub const V0: Self = Self(0); /// Version `1` of the protocol. /// @@ -25,16 +35,18 @@ impl ProtocolVersion { /// Version `2` of the protocol. /// /// This is an unstable draft used for protocol iteration. It is only - /// available when the `unstable_protocol_v2` feature is enabled and is - /// **not** advertised by [`ProtocolVersion::LATEST`] yet — callers must - /// opt into V2 explicitly. + /// available when the `unstable_protocol_v2` feature is enabled and must + /// be selected explicitly. #[cfg(feature = "unstable_protocol_v2")] pub const V2: Self = Self(2); /// The latest stable supported version of the protocol. /// - /// Currently this is version `1`. Enabling the `unstable_protocol_v2` - /// feature exposes `ProtocolVersion::V2` but does **not** change the - /// value of `LATEST` — v2 will only become the latest once it stabilizes. + /// Currently this is version `1`. + /// + /// This shorthand is intentionally unavailable when the + /// `unstable_protocol_v2` feature is enabled, so code that opts into the + /// v2 draft must choose `V1` or `V2` explicitly. + #[cfg(not(feature = "unstable_protocol_v2"))] pub const LATEST: Self = Self::V1; /// Returns the numeric protocol version. @@ -50,56 +62,6 @@ impl ProtocolVersion { } } -use serde::{Deserialize, Deserializer}; - -impl<'de> Deserialize<'de> for ProtocolVersion { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - use serde::de::{self, Visitor}; - use std::fmt; - - struct ProtocolVersionVisitor; - - impl Visitor<'_> for ProtocolVersionVisitor { - type Value = ProtocolVersion; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("a protocol version number or string") - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - match u16::try_from(value) { - Ok(value) => Ok(ProtocolVersion(value)), - Err(_) => Err(E::custom(format!("protocol version {value} is too large"))), - } - } - - fn visit_str(self, _value: &str) -> Result - where - E: de::Error, - { - // Old versions used strings, we consider all of those version 0 - Ok(ProtocolVersion::V0) - } - - fn visit_string(self, _value: String) -> Result - where - E: de::Error, - { - // Old versions used strings, we consider all of those version 0 - Ok(ProtocolVersion::V0) - } - } - - deserializer.deserialize_any(ProtocolVersionVisitor) - } -} - #[cfg(test)] mod tests { use super::*; @@ -112,10 +74,10 @@ mod tests { } #[test] - fn test_deserialize_string() { + fn test_deserialize_string_errors() { let json = "\"1.0.0\""; - let version: ProtocolVersion = serde_json::from_str(json).unwrap(); - assert_eq!(version, ProtocolVersion::new(0)); + let result: Result = serde_json::from_str(json); + assert!(result.is_err()); } #[test] @@ -143,6 +105,8 @@ mod tests { fn test_as_u16() { assert_eq!(ProtocolVersion::V0.as_u16(), 0); assert_eq!(ProtocolVersion::V1.as_u16(), 1); + + #[cfg(not(feature = "unstable_protocol_v2"))] assert_eq!(ProtocolVersion::LATEST.as_u16(), 1); #[cfg(feature = "unstable_protocol_v2")] diff --git a/docs/rfds/elicitation.mdx b/docs/rfds/elicitation.mdx index ab69c1082..d708a8181 100644 --- a/docs/rfds/elicitation.mdx +++ b/docs/rfds/elicitation.mdx @@ -628,7 +628,7 @@ Clients declare elicitation support during the `initialize` phase via `ClientCap "jsonrpc": "2.0", "method": "initialize", "params": { - "protocolVersion": "2025-11-25", + "protocolVersion": 1, "clientCapabilities": { "fs": { "readTextFile": true, diff --git a/schema-generator/src/main.rs b/schema-generator/src/main.rs index 4a05f70c9..70175aeaf 100644 --- a/schema-generator/src/main.rs +++ b/schema-generator/src/main.rs @@ -175,10 +175,9 @@ fn write_schema(schema_value: &serde_json::Value, schema_dir: &Path, docs_protoc .unwrap_or_else(|e| panic!("Failed to write {schema_file}: {e}")); // The version embedded in `meta*.json` reflects the protocol version the - // *schema itself describes*, not `ProtocolVersion::LATEST` (which always - // tracks the latest **stable** version). Generating with the - // `unstable_protocol_v2` feature emits v2-shaped types, so the metadata - // file must advertise version 2 to stay consistent with its contents. + // *schema itself describes*. Generating with the `unstable_protocol_v2` + // feature emits v2-shaped types, so the metadata file must advertise + // version 2 to stay consistent with its contents. #[cfg(feature = "unstable_protocol_v2")] let schema_protocol_version = ProtocolVersion::V2; #[cfg(not(feature = "unstable_protocol_v2"))]