1use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub enum DealCapability {
16 DataLookup,
17 DataTransform,
18 Compute,
19 Verification,
20 Custom(String),
24}
25
26impl DealCapability {
27 pub fn as_str(&self) -> &str {
28 match self {
29 DealCapability::DataLookup => "data_lookup",
30 DealCapability::DataTransform => "data_transform",
31 DealCapability::Compute => "compute",
32 DealCapability::Verification => "verification",
33 DealCapability::Custom(s) => s.as_str(),
34 }
35 }
36}
37
38pub type Ed25519PubBytes = [u8; 32];
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43pub struct AgentAdvertisement {
44 pub agent: String,
45 pub capabilities: Vec<DealCapability>,
46 pub public_key_hex: String,
48 pub ledger_anchor_url: String,
49 pub terms_template: serde_json::Value,
50}
51
52impl AgentAdvertisement {
53 pub fn new(
54 agent: impl Into<String>,
55 capabilities: Vec<DealCapability>,
56 public_key: &Ed25519PubBytes,
57 ledger_anchor_url: impl Into<String>,
58 ) -> Self {
59 Self {
60 agent: agent.into(),
61 capabilities,
62 public_key_hex: hex::encode(public_key),
63 ledger_anchor_url: ledger_anchor_url.into(),
64 terms_template: serde_json::json!({}),
65 }
66 }
67
68 pub fn to_well_known(&self) -> Result<String, serde_json::Error> {
70 serde_json::to_string_pretty(self)
71 }
72
73 pub fn from_well_known(body: &str) -> Result<Self, serde_json::Error> {
75 serde_json::from_str(body)
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn advertisement_round_trips_through_json() {
85 let pk = [7u8; 32];
86 let ad = AgentAdvertisement::new(
87 "agent-runner-42",
88 vec![DealCapability::DataLookup, DealCapability::Compute],
89 &pk,
90 "https://agent-42.example.com/deal-ledger",
91 );
92 let body = ad.to_well_known().unwrap();
93 let restored = AgentAdvertisement::from_well_known(&body).unwrap();
94 assert_eq!(restored, ad);
95 }
96
97 #[test]
98 fn capability_strings_round_trip() {
99 assert_eq!(DealCapability::DataLookup.as_str(), "data_lookup");
100 let custom = DealCapability::Custom("research".into());
101 assert_eq!(custom.as_str(), "research");
102 }
103
104 #[test]
105 fn body_includes_required_fields() {
106 let ad = AgentAdvertisement::new(
107 "x",
108 vec![DealCapability::DataLookup],
109 &[1u8; 32],
110 "https://x/y",
111 );
112 let body = ad.to_well_known().unwrap();
113 for required in [
114 "agent",
115 "capabilities",
116 "public_key_hex",
117 "ledger_anchor_url",
118 ] {
119 assert!(
120 body.contains(required),
121 "missing field {required} in: {body}"
122 );
123 }
124 }
125}