feagi_agent/sdk/common/
agent_descriptor.rs1use base64::{engine::general_purpose, Engine as _};
2use feagi_structures::FeagiDataError;
3
4const MAX_MANUFACTURER_LENGTH: usize = 20;
5const MAX_AGENT_NAME_LENGTH: usize = 20;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub struct AgentDescriptor {
9 instance_id: u32,
10 manufacturer: [u8; MAX_MANUFACTURER_LENGTH], agent_name: [u8; MAX_AGENT_NAME_LENGTH], agent_version: u32,
13}
14
15impl AgentDescriptor {
16 pub const SIZE_BYTES: usize = 4 + MAX_MANUFACTURER_LENGTH + MAX_AGENT_NAME_LENGTH + 4; pub fn new(
20 instance_id: u32,
21 manufacturer: &str,
22 agent_name: &str,
23 agent_version: u32,
24 ) -> Result<Self, FeagiDataError> {
25 if !manufacturer.is_ascii() || !agent_name.is_ascii() {
26 return Err(FeagiDataError::BadParameters(
27 "ASCII characters only!".to_string(),
28 ));
29 }
30
31 if manufacturer.len() > MAX_MANUFACTURER_LENGTH {
32 return Err(FeagiDataError::BadParameters(format!(
33 "Manufacturer is too long! Max length is {} characters!",
34 MAX_MANUFACTURER_LENGTH
35 )));
36 }
37 if agent_name.len() > MAX_AGENT_NAME_LENGTH {
38 return Err(FeagiDataError::BadParameters(format!(
39 "Agent name is too long! Max length is {} characters!",
40 MAX_AGENT_NAME_LENGTH
41 )));
42 }
43 if agent_version == 0 {
44 return Err(FeagiDataError::BadParameters(
45 "Agent Version cannot be zero!".to_string(),
46 ));
47 }
48
49 let mut manufacturer_bytes = [0u8; MAX_MANUFACTURER_LENGTH];
51 manufacturer_bytes[..manufacturer.len()].copy_from_slice(manufacturer.as_bytes());
52
53 let mut agent_name_bytes = [0u8; MAX_AGENT_NAME_LENGTH];
54 agent_name_bytes[..agent_name.len()].copy_from_slice(agent_name.as_bytes());
55
56 Ok(AgentDescriptor {
57 instance_id,
58 manufacturer: manufacturer_bytes,
59 agent_name: agent_name_bytes,
60 agent_version,
61 })
62 }
63
64 pub fn instance_id(&self) -> u32 {
66 self.instance_id
67 }
68
69 pub fn manufacturer(&self) -> &str {
71 let end = self
72 .manufacturer
73 .iter()
74 .position(|&b| b == 0)
75 .unwrap_or(MAX_MANUFACTURER_LENGTH);
76 std::str::from_utf8(&self.manufacturer[..end]).unwrap_or("")
78 }
79
80 pub fn agent_name(&self) -> &str {
82 let end = self
83 .agent_name
84 .iter()
85 .position(|&b| b == 0)
86 .unwrap_or(MAX_AGENT_NAME_LENGTH);
87 std::str::from_utf8(&self.agent_name[..end]).unwrap_or("")
89 }
90
91 pub fn agent_version(&self) -> u32 {
93 self.agent_version
94 }
95
96 pub fn manufacturer_bytes(&self) -> &[u8; MAX_MANUFACTURER_LENGTH] {
98 &self.manufacturer
99 }
100
101 pub fn agent_name_bytes(&self) -> &[u8; MAX_AGENT_NAME_LENGTH] {
103 &self.agent_name
104 }
105
106 #[allow(clippy::wrong_self_convention)]
108 pub fn to_bytes(&self) -> [u8; Self::SIZE_BYTES] {
109 let mut bytes = [0u8; Self::SIZE_BYTES];
110 let mut offset = 0;
111
112 bytes[offset..offset + 4].copy_from_slice(&self.instance_id.to_le_bytes());
114 offset += 4;
115
116 bytes[offset..offset + MAX_MANUFACTURER_LENGTH].copy_from_slice(&self.manufacturer);
118 offset += MAX_MANUFACTURER_LENGTH;
119
120 bytes[offset..offset + MAX_AGENT_NAME_LENGTH].copy_from_slice(&self.agent_name);
122 offset += MAX_AGENT_NAME_LENGTH;
123
124 bytes[offset..offset + 4].copy_from_slice(&self.agent_version.to_le_bytes());
126
127 bytes
128 }
129
130 pub fn from_bytes(bytes: &[u8; Self::SIZE_BYTES]) -> Result<Self, FeagiDataError> {
132 let mut offset = 0;
133
134 let instance_id = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
136 offset += 4;
137
138 let mut manufacturer = [0u8; MAX_MANUFACTURER_LENGTH];
140 manufacturer.copy_from_slice(&bytes[offset..offset + MAX_MANUFACTURER_LENGTH]);
141 offset += MAX_MANUFACTURER_LENGTH;
142
143 for &b in &manufacturer {
145 if b != 0 && !b.is_ascii() {
146 return Err(FeagiDataError::DeserializationError(
147 "Manufacturer contains non-ASCII characters".to_string(),
148 ));
149 }
150 }
151
152 let mut agent_name = [0u8; MAX_AGENT_NAME_LENGTH];
154 agent_name.copy_from_slice(&bytes[offset..offset + MAX_AGENT_NAME_LENGTH]);
155 offset += MAX_AGENT_NAME_LENGTH;
156
157 for &b in &agent_name {
159 if b != 0 && !b.is_ascii() {
160 return Err(FeagiDataError::DeserializationError(
161 "Agent name contains non-ASCII characters".to_string(),
162 ));
163 }
164 }
165
166 let agent_version = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
168
169 if agent_version == 0 {
170 return Err(FeagiDataError::DeserializationError(
171 "Agent Version cannot be zero!".to_string(),
172 ));
173 }
174
175 Ok(AgentDescriptor {
176 instance_id,
177 manufacturer,
178 agent_name,
179 agent_version,
180 })
181 }
182
183 pub fn to_base64(&self) -> String {
185 general_purpose::STANDARD.encode(self.to_bytes())
186 }
187
188 pub fn try_from_base64(encoded: &str) -> Result<Self, FeagiDataError> {
190 let decoded = general_purpose::STANDARD.decode(encoded).map_err(|e| {
191 FeagiDataError::DeserializationError(format!("Failed to decode base64 string: {}", e))
192 })?;
193
194 if decoded.len() != Self::SIZE_BYTES {
195 return Err(FeagiDataError::DeserializationError(format!(
196 "Invalid AgentDescriptor length: expected {} bytes, got {}",
197 Self::SIZE_BYTES,
198 decoded.len()
199 )));
200 }
201
202 let mut bytes = [0u8; Self::SIZE_BYTES];
203 bytes.copy_from_slice(&decoded);
204 Self::from_bytes(&bytes)
205 }
206}