Skip to main content

plato_i2i/
lib.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5/// A single I2I inter-agent message.
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub struct I2IMessage {
8    /// Identifier of the sending agent.
9    pub sender: String,
10    /// Identifier of the intended receiving agent.
11    pub recipient: String,
12    /// Logical message type (e.g., `QUERY`, `RESPONSE`).
13    pub msg_type: String,
14    /// Payload of the message.
15    pub content: String,
16    /// Priority level -- `P0`, `P1`, or `P2` (lowest).
17    #[serde(default = "default_priority")]
18    pub priority: String,
19    /// Unix epoch timestamp (float) when the message was created.
20    #[serde(default = "current_timestamp")]
21    pub timestamp: f64,
22    /// Additional key/value context attached to the message.
23    #[serde(default = "HashMap::new")]
24    pub metadata: HashMap<String, serde_json::Value>,
25}
26
27fn default_priority() -> String {
28    "P2".to_string()
29}
30
31fn current_timestamp() -> f64 {
32    SystemTime::now()
33        .duration_since(UNIX_EPOCH)
34        .unwrap_or_default()
35        .as_secs_f64()
36}
37
38impl I2IMessage {
39    /// Create a new I2IMessage with the current timestamp.
40    pub fn new(
41        sender: impl Into<String>,
42        recipient: impl Into<String>,
43        msg_type: impl Into<String>,
44        content: impl Into<String>,
45    ) -> Self {
46        Self {
47            sender: sender.into(),
48            recipient: recipient.into(),
49            msg_type: msg_type.into(),
50            content: content.into(),
51            priority: default_priority(),
52            timestamp: current_timestamp(),
53            metadata: HashMap::new(),
54        }
55    }
56
57    /// Create a new I2IMessage with a specific priority.
58    pub fn with_priority(
59        sender: impl Into<String>,
60        recipient: impl Into<String>,
61        msg_type: impl Into<String>,
62        content: impl Into<String>,
63        priority: impl Into<String>,
64    ) -> Self {
65        Self {
66            sender: sender.into(),
67            recipient: recipient.into(),
68            msg_type: msg_type.into(),
69            content: content.into(),
70            priority: priority.into(),
71            timestamp: current_timestamp(),
72            metadata: HashMap::new(),
73        }
74    }
75}
76
77/// Message formatting, parsing, serialization, and validation utilities.
78pub struct I2IProtocol;
79
80impl I2IProtocol {
81    /// Create a fully populated [`I2IMessage`].
82    pub fn format_message(
83        sender: impl Into<String>,
84        recipient: impl Into<String>,
85        msg_type: impl Into<String>,
86        content: impl Into<String>,
87        priority: impl Into<String>,
88    ) -> I2IMessage {
89        I2IMessage::with_priority(sender, recipient, msg_type, content, priority)
90    }
91
92    /// Parse a human-readable `[I2I:TYPE] sender -> recipient — content` string.
93    ///
94    /// # Errors
95    /// Returns an error if *raw* does not conform to the expected pattern.
96    pub fn parse_message(raw: &str) -> Result<I2IMessage, String> {
97        if !raw.starts_with("[I2I:") {
98            return Err("Message must start with '[I2I:'".to_string());
99        }
100
101        let type_end = raw[5..].find(']').ok_or("Missing closing ']' for message type")? + 5;
102        let msg_type = raw[5..type_end].trim().to_string();
103        let remainder = raw[type_end + 1..].trim();
104
105        let arrow = remainder.find("->").ok_or("Missing '->' separator")?;
106        let sender = remainder[..arrow].trim().to_string();
107        let after_arrow = remainder[arrow + 2..].trim();
108
109        let em_dash = after_arrow.find('—');
110        let (recipient, content) = match em_dash {
111            Some(idx) => (
112                after_arrow[..idx].trim().to_string(),
113                after_arrow[idx + 1..].trim().to_string(),
114            ),
115            None => {
116                let fallback = after_arrow.find("- ").ok_or("Missing '—' content separator")?;
117                (
118                    after_arrow[..fallback].trim().to_string(),
119                    after_arrow[fallback + 1..].trim().to_string(),
120                )
121            }
122        };
123
124        Ok(I2IMessage {
125            sender,
126            recipient,
127            msg_type,
128            content,
129            priority: default_priority(),
130            timestamp: current_timestamp(),
131            metadata: HashMap::new(),
132        })
133    }
134
135    /// Convert an [`I2IMessage`] to a wire-format JSON string.
136    pub fn serialize(message: &I2IMessage) -> String {
137        serde_json::to_string(message).unwrap_or_default()
138    }
139
140    /// Reconstruct an [`I2IMessage`] from a JSON wire-format string.
141    ///
142    /// # Errors
143    /// Returns an error if *raw* is not valid JSON or missing required keys.
144    pub fn deserialize(raw: &str) -> Result<I2IMessage, String> {
145        let payload: serde_json::Value = serde_json::from_str(raw).map_err(|e| format!("Invalid JSON: {e}"))?;
146
147        let required = ["sender", "recipient", "msg_type", "content"];
148        for key in &required {
149            if payload.get(key).is_none() {
150                return Err(format!("Missing required field: {key}"));
151            }
152        }
153
154        let message: I2IMessage = serde_json::from_value(payload)
155            .map_err(|e| format!("Invalid message structure: {e}"))?;
156        Ok(message)
157    }
158
159    /// Check whether *message* has all required fields populated.
160    ///
161    /// A field is considered populated when it is a non-empty string.
162    pub fn validate(message: &I2IMessage) -> bool {
163        !message.sender.trim().is_empty()
164            && !message.recipient.trim().is_empty()
165            && !message.msg_type.trim().is_empty()
166            && !message.content.trim().is_empty()
167    }
168
169    /// Block messages whose priority is below the supplied threshold.
170    ///
171    /// Lower numeric rank == higher priority (`P0` > `P1` > `P2`).
172    pub fn priority_gate(message: &I2IMessage, min_priority: &str) -> bool {
173        let rank = |p: &str| match p {
174            "P0" => 0,
175            "P1" => 1,
176            "P2" => 2,
177            _ => 99,
178        };
179        rank(&message.priority) <= rank(min_priority)
180    }
181}
182
183/// Routes messages to the most trusted agent among a set of candidates.
184///
185/// Trust levels are floats in the inclusive range `0.0` to `1.0`.
186#[derive(Debug, Clone, Default)]
187pub struct TrustRouter {
188    trust: HashMap<String, f64>,
189}
190
191impl TrustRouter {
192    /// Initialise an empty trust table.
193    pub fn new() -> Self {
194        Self {
195            trust: HashMap::new(),
196        }
197    }
198
199    /// Register or update an agent's trust level.
200    ///
201    /// # Errors
202    /// Returns an error if *trust_level* is outside the `[0.0, 1.0]` range.
203    pub fn add_trust(&mut self, agent_id: impl Into<String>, trust_level: f64) -> Result<(), String> {
204        if !(0.0..=1.0).contains(&trust_level) {
205            return Err("trust_level must be between 0.0 and 1.0".to_string());
206        }
207        self.trust.insert(agent_id.into(), trust_level);
208        Ok(())
209    }
210
211    /// Return the stored trust level for *agent_id*.
212    ///
213    /// Returns `0.0` if the agent is not known.
214    pub fn get_trust(&self, agent_id: &str) -> f64 {
215        self.trust.get(agent_id).copied().unwrap_or(0.0)
216    }
217
218    /// Pick the most-trusted agent from *available_agents*.
219    ///
220    /// Returns the identifier of the most trusted available agent, or `None` if
221    /// no candidates are supplied or none are trusted.
222    pub fn route(&self, _message: &I2IMessage, available_agents: &[String]) -> Option<String> {
223        if available_agents.is_empty() {
224            return None;
225        }
226
227        let mut best_agent: Option<String> = None;
228        let mut best_score = -1.0;
229
230        for agent in available_agents {
231            let score = self.get_trust(agent);
232            if score > best_score {
233                best_score = score;
234                best_agent = Some(agent.clone());
235            }
236        }
237
238        if best_score <= 0.0 {
239            return None;
240        }
241
242        best_agent
243    }
244
245    /// Check whether *agent_id* meets the minimum trust threshold.
246    pub fn is_trusted(&self, agent_id: &str, min_trust: f64) -> bool {
247        self.get_trust(agent_id) >= min_trust
248    }
249
250    /// Return aggregate statistics for the trust table.
251    pub fn stats(&self) -> HashMap<String, serde_json::Value> {
252        let mut result = HashMap::new();
253        if self.trust.is_empty() {
254            result.insert("count".to_string(), serde_json::json!(0));
255            result.insert("average_trust".to_string(), serde_json::json!(0.0));
256            result.insert("max_trust".to_string(), serde_json::json!(0.0));
257            result.insert("min_trust".to_string(), serde_json::json!(0.0));
258            return result;
259        }
260
261        let values: Vec<f64> = self.trust.values().copied().collect();
262        let count = values.len() as i64;
263        let sum: f64 = values.iter().sum();
264        let avg = sum / values.len() as f64;
265        let max = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
266        let min = values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
267
268        result.insert("count".to_string(), serde_json::json!(count));
269        result.insert("average_trust".to_string(), serde_json::json!(avg));
270        result.insert("max_trust".to_string(), serde_json::json!(max));
271        result.insert("min_trust".to_string(), serde_json::json!(min));
272        result
273    }
274}