use ai_registry_api::{
consts::{AGENT, FLAG_KNOWLEDGE_FINALIZED, MAX_KNOWLEDGE_REFERENCE_LENGTH},
error::AgentError,
event::KnowledgeConfiguredEvent,
instruction::ConfigureKnowledge,
state::Agent,
};
use steel::*;
fn is_valid_cid(bytes: &[u8]) -> bool {
if let Ok(s) = core::str::from_utf8(bytes) {
if s.is_empty() || s.len() > MAX_KNOWLEDGE_REFERENCE_LENGTH {
return false;
}
if s.starts_with("bafy") {
return s.chars().all(|c| matches!(c, 'a'..='z' | '2'..='7'));
} else if s.starts_with("Qm") {
return s.len() == 46
&& s.chars().all(|c| {
matches!(
c,
'1'..='9'
| 'A'..='H'
| 'J'..='N'
| 'P'..='Z'
| 'a'..='k'
| 'm'..='z'
)
});
}
}
false
}
pub fn process_configure_knowledge(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
let args = ConfigureKnowledge::try_from_bytes(data)?;
let [controller_info, agent_info] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
agent_info.is_writable()?.has_seeds(
&[
AGENT,
agent_info
.as_account::<Agent>(&ai_registry_api::ID)?
.creator
.as_ref(),
],
&ai_registry_api::ID,
)?;
controller_info.is_signer()?;
let agent = agent_info.as_account::<Agent>(&ai_registry_api::ID)?;
if controller_info.key != &agent.controller {
return Err(AgentError::UnauthorizedController.into());
}
if agent.state_flags & FLAG_KNOWLEDGE_FINALIZED != 0 {
return Err(AgentError::KnowledgeFinalized.into());
}
let ref_len = args
.reference
.iter()
.position(|&b| b == 0)
.unwrap_or(MAX_KNOWLEDGE_REFERENCE_LENGTH);
let ref_bytes = &args.reference[..ref_len];
let zero_hash = [0u8; 32];
match args.reference_type {
0 => {
if !ref_bytes.is_empty() || args.content_hash != zero_hash {
return Err(AgentError::InvalidKnowledgeFields.into());
}
}
1 => {
if ref_bytes.is_empty() {
return Err(AgentError::InvalidKnowledgeFields.into());
}
if !is_valid_cid(ref_bytes) {
return Err(AgentError::InvalidKnowledgeFields.into());
}
if args.content_hash != zero_hash {
return Err(AgentError::InvalidKnowledgeFields.into());
}
}
2 => {
if ref_bytes.is_empty() {
return Err(AgentError::InvalidKnowledgeFields.into());
}
let url_str =
core::str::from_utf8(ref_bytes).map_err(|_| AgentError::InvalidKnowledgeFields)?;
if !url_str.starts_with("https://") {
return Err(AgentError::InsecureUri.into());
}
if args.content_hash == zero_hash {
return Err(AgentError::InvalidKnowledgeFields.into());
}
}
3 => {
if ref_bytes.is_empty() {
return Err(AgentError::InvalidKnowledgeFields.into());
}
if args.content_hash == zero_hash {
return Err(AgentError::InvalidKnowledgeFields.into());
}
}
4 => {
if ref_bytes.is_empty() {
return Err(AgentError::InvalidKnowledgeFields.into());
}
if args.content_hash == zero_hash {
return Err(AgentError::InvalidKnowledgeFields.into());
}
}
_ => return Err(AgentError::InvalidKnowledgeFields.into()),
}
let clock = Clock::get()?;
let agent = agent_info.as_account_mut::<Agent>(&ai_registry_api::ID)?;
agent.knowledge_reference_type = args.reference_type;
if args.reference_type == 0 {
agent.knowledge_reference = [0u8; 128];
agent.knowledge_reference_len = 0;
agent.knowledge_content_hash = zero_hash;
} else {
agent.knowledge_reference = args.reference;
agent.knowledge_reference_len = ref_len as u8;
agent.knowledge_content_hash = args.content_hash;
}
agent.updated_at = clock.unix_timestamp;
let mut ref_preview = [0u8; 32];
let preview_len = core::cmp::min(32, ref_len);
if preview_len > 0 {
ref_preview[..preview_len].copy_from_slice(&ref_bytes[..preview_len]);
}
KnowledgeConfiguredEvent {
creator: agent.creator,
agent: *agent_info.key,
reference_type: args.reference_type,
reference_preview: ref_preview,
content_hash: args.content_hash,
timestamp: clock.unix_timestamp,
_padding: [0; 7],
}
.log();
Ok(())
}