use core::fmt;
use crate::domain::{Feature, FeatureProfile, ProtocolVersion, resolve};
#[cfg(feature = "a2a")]
pub mod a2a_mcp;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Leg {
pub version: ProtocolVersion,
pub profile: FeatureProfile,
}
impl Leg {
#[must_use]
pub const fn new(version: ProtocolVersion, profile: FeatureProfile) -> Self {
Self { version, profile }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Withheld {
pub feature: Feature,
pub refused_by: RefusedBy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RefusedBy {
Upstream,
Downstream,
}
impl fmt::Display for RefusedBy {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Upstream => formatter.write_str("the upstream server"),
Self::Downstream => formatter.write_str("the downstream client"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Agreement {
downstream: Leg,
upstream: Leg,
served: FeatureProfile,
withheld: Vec<Withheld>,
}
impl Agreement {
#[must_use]
pub fn between(downstream: Leg, upstream: Leg) -> Self {
let served = downstream.profile.intersection(upstream.profile);
let withheld = downstream
.profile
.difference(served)
.iter()
.map(|feature| Withheld {
feature,
refused_by: RefusedBy::Downstream,
})
.chain(
upstream
.profile
.difference(served)
.iter()
.map(|feature| Withheld {
feature,
refused_by: RefusedBy::Upstream,
}),
)
.collect();
Self {
downstream,
upstream,
served,
withheld,
}
}
#[must_use]
pub const fn downstream(&self) -> &Leg {
&self.downstream
}
#[must_use]
pub const fn upstream(&self) -> &Leg {
&self.upstream
}
#[must_use]
pub const fn served(&self) -> FeatureProfile {
self.served
}
#[must_use]
pub fn withheld(&self) -> &[Withheld] {
&self.withheld
}
#[must_use]
pub fn serves(&self, feature: Feature) -> bool {
self.served.allows(feature)
}
#[must_use]
pub fn same_version(&self) -> bool {
self.downstream.version == self.upstream.version
}
#[must_use]
pub fn summary(&self) -> String {
format!(
"client {} / server {} — {} features served, {} withheld",
self.downstream.version,
self.upstream.version,
self.served.iter().count(),
self.withheld.len()
)
}
}
#[must_use]
pub fn agree(
downstream: crate::domain::Negotiation,
upstream: crate::domain::Negotiation,
) -> Agreement {
Agreement::between(
Leg::new(downstream.version, resolve(downstream)),
Leg::new(upstream.version, resolve(upstream)),
)
}
#[cfg(test)]
mod tests {
use crate::domain::{
Feature, FeatureProfile, Negotiation, ProtocolVersion, TransportKind, agree,
};
use super::{Agreement, Leg, RefusedBy};
type TestError = Box<dyn std::error::Error>;
fn profile_with(features: &[Feature]) -> FeatureProfile {
features
.iter()
.fold(FeatureProfile::empty(), |profile, feature| {
profile.with(*feature)
})
}
#[test]
fn a_feature_only_the_client_has_is_not_served() {
let agreement = Agreement::between(
Leg::new(
ProtocolVersion::latest_known(),
profile_with(&[Feature::Tools, Feature::Resources]),
),
Leg::new(
ProtocolVersion::latest_known(),
profile_with(&[Feature::Tools]),
),
);
assert!(
agreement.serves(Feature::Tools),
"the shared feature vanished"
);
assert!(
!agreement.serves(Feature::Resources),
"the bridge advertised a feature the server cannot serve"
);
}
#[test]
fn each_withheld_feature_names_its_refuser() {
let agreement = Agreement::between(
Leg::new(
ProtocolVersion::latest_known(),
profile_with(&[Feature::Tools, Feature::Resources]),
),
Leg::new(
ProtocolVersion::latest_known(),
profile_with(&[Feature::Tools, Feature::Prompts]),
),
);
let refused: Vec<(&str, RefusedBy)> = agreement
.withheld()
.iter()
.map(|entry| (entry.feature.as_str(), entry.refused_by))
.collect();
assert!(
refused.contains(&("resources", RefusedBy::Downstream)),
"the client's own feature was not attributed to the client: {refused:?}"
);
assert!(
refused.contains(&("prompts", RefusedBy::Upstream)),
"the server's missing feature was not attributed to the server: {refused:?}"
);
}
#[test]
fn sides_with_nothing_in_common_still_agree() {
let agreement = Agreement::between(
Leg::new(
ProtocolVersion::latest_known(),
profile_with(&[Feature::Resources]),
),
Leg::new(
ProtocolVersion::latest_known(),
profile_with(&[Feature::Prompts]),
),
);
assert!(agreement.served().is_empty(), "something was invented");
assert_eq!(agreement.withheld().len(), 2);
}
#[test]
fn identical_legs_withhold_nothing() {
let features = profile_with(&[Feature::Tools, Feature::Resources, Feature::Prompts]);
let agreement = Agreement::between(
Leg::new(ProtocolVersion::latest_known(), features),
Leg::new(ProtocolVersion::latest_known(), features),
);
assert!(
agreement.withheld().is_empty(),
"{:?}",
agreement.withheld()
);
}
#[test]
fn the_two_versions_are_kept_apart() -> Result<(), TestError> {
let negotiation = |version| Negotiation::bare(version, TransportKind::StreamableHttp);
let newer = ProtocolVersion::latest_known();
let older = "2024-11-05".parse()?;
let agreement = agree(negotiation(older), negotiation(newer));
assert_eq!(agreement.downstream().version, older);
assert_eq!(agreement.upstream().version, newer);
assert!(!agreement.same_version());
Ok(())
}
}