Skip to main content

aph_core/
a2a_extension.rs

1//! A2A Agent Card extension descriptor for APH.
2//!
3//! When an agent advertises APH support on its AgentCard, it adds the
4//! extension declaration produced by `aph_a2a_extension()`. Verifiers can
5//! discover APH support by scanning `agent_card.extensions` for the
6//! `APH_EXTENSION_URI`.
7//!
8//! The `AgentExtension` shape is vendored here from the A2A Agent Card
9//! model: an opaque capability declaration (uri / description / required)
10//! that peers who do not understand it safely ignore.
11
12/// An extension capability declared on an Agent Card.
13#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14pub struct AgentExtension {
15  uri: String,
16  description: String,
17  required: bool,
18}
19
20impl AgentExtension {
21  /// Declares an extension capability with the given URI, description, and
22  /// whether peers must understand it to interoperate.
23  pub fn new(uri: impl Into<String>, description: impl Into<String>, required: bool) -> Self {
24    Self {
25      uri: uri.into(),
26      description: description.into(),
27      required,
28    }
29  }
30
31  /// Opaque extension identifier, compared byte-for-byte by peers.
32  pub fn uri(&self) -> &str {
33    &self.uri
34  }
35  /// Human-readable summary shown when inspecting an Agent Card.
36  pub fn description(&self) -> &str {
37    &self.description
38  }
39  /// Whether a peer must understand this extension to interoperate.
40  pub fn required(&self) -> bool {
41    self.required
42  }
43}
44
45/// URI for the APH notarization extension on the AgentCard.
46pub const APH_EXTENSION_URI: &str = "aph://extensions/notarization/v1";
47
48/// Creates the APH notarization extension declaration.
49pub fn aph_a2a_extension() -> crate::a2a_extension::AgentExtension {
50  crate::a2a_extension::AgentExtension::new(
51    APH_EXTENSION_URI,
52    "APH (Agent per Human) outbound communication notarization",
53    false,
54  )
55}
56
57#[cfg(test)]
58mod tests {
59  #[test]
60  fn aph_uri_pinned() {
61    // The A2A extension spec requires this URI be declared as one exact
62    // constant and compared byte-for-byte, never assembled from parts. A
63    // single character of drift makes peers fail to recognize APH support.
64    std::assert_eq!(super::APH_EXTENSION_URI, "aph://extensions/notarization/v1");
65  }
66
67  #[test]
68  fn aph_extension_is_optional() {
69    // required:false is deliberate — advertising APH must not make peers
70    // that lack it unable to talk to us. Flipping this to true would break
71    // interop with every non-APH A2A agent.
72    let ext = super::aph_a2a_extension();
73    std::assert_eq!(ext.uri(), super::APH_EXTENSION_URI);
74    std::assert!(!ext.required());
75  }
76
77  #[test]
78  fn description_is_non_empty() {
79    // The description is what a human sees when inspecting an AgentCard's
80    // capabilities; empty would leave the extension unexplained there.
81    let ext = super::aph_a2a_extension();
82    std::assert!(!ext.description().is_empty());
83  }
84}