Skip to main content

feagi_io/
agent_id.rs

1use crate::FeagiNetworkError;
2use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
3use feagi_serialization::{AgentIdentifier, FeagiByteContainer};
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6/// Agent identifier: 48-byte AgentDescriptor (instance_id(4) + manufacturer(20) + agent_name(20) + version(4)).
7/// Single format - no 8-byte legacy.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub struct AgentID {
10    bytes: [u8; AgentID::NUMBER_BYTES],
11}
12
13impl Serialize for AgentID {
14    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
15    where
16        S: Serializer,
17    {
18        serializer.serialize_str(&self.to_base64())
19    }
20}
21
22impl<'de> Deserialize<'de> for AgentID {
23    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
24    where
25        D: Deserializer<'de>,
26    {
27        let s = String::deserialize(deserializer)?;
28        Self::try_from_base64(&s).map_err(serde::de::Error::custom)
29    }
30}
31
32impl AgentID {
33    pub const NUMBER_BYTES: usize = FeagiByteContainer::AGENT_ID_BYTE_COUNT;
34
35    pub fn new(bytes: [u8; AgentID::NUMBER_BYTES]) -> Self {
36        Self { bytes }
37    }
38
39    pub const fn new_blank() -> Self {
40        Self {
41            bytes: [0; AgentID::NUMBER_BYTES],
42        }
43    }
44
45    pub fn new_random() -> Self {
46        let mut bytes = [0u8; AgentID::NUMBER_BYTES];
47        getrandom::getrandom(&mut bytes).expect("Failed to generate random bytes");
48        Self { bytes }
49    }
50
51    /// Attempts to create an AgentID from a base64-encoded string.
52    ///
53    /// # Arguments
54    ///
55    /// * `base64_str` - A base64-encoded string representing the agent ID bytes.
56    ///
57    /// # Errors
58    ///
59    /// Returns an error if:
60    /// - The string is not valid base64
61    /// - The decoded bytes length doesn't match `NUMBER_BYTES`
62    pub fn try_from_base64(base64_str: &str) -> Result<Self, FeagiNetworkError> {
63        let decoded = BASE64_STANDARD
64            .decode(base64_str)
65            .map_err(|e| FeagiNetworkError::GeneralFailure(format!("Invalid base64: {}", e)))?;
66
67        if decoded.len() != Self::NUMBER_BYTES {
68            return Err(FeagiNetworkError::GeneralFailure(format!(
69                "Invalid AgentID length: expected {} bytes, got {}",
70                Self::NUMBER_BYTES,
71                decoded.len()
72            )));
73        }
74
75        let mut bytes = [0u8; Self::NUMBER_BYTES];
76        bytes.copy_from_slice(&decoded);
77        Ok(Self { bytes })
78    }
79
80    pub fn is_blank(&self) -> bool {
81        self.bytes == [0; AgentID::NUMBER_BYTES]
82    }
83
84    pub fn bytes(&self) -> &[u8; AgentID::NUMBER_BYTES] {
85        &self.bytes
86    }
87
88    /// Encodes the agent ID bytes as a base64 string.
89    pub fn to_base64(&self) -> String {
90        BASE64_STANDARD.encode(self.bytes)
91    }
92}
93
94impl AgentIdentifier for AgentID {
95    fn get_identifier_bytes(&self) -> &[u8; FeagiByteContainer::AGENT_ID_BYTE_COUNT] {
96        &self.bytes
97    }
98}