use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct AgentId(pub String);
impl AgentId {
pub fn new(id: impl Into<String>) -> Self {
let id = id.into();
if id.is_empty() {
debug_assert!(false, "AgentId must not be empty");
tracing::warn!("AgentId::new called with an empty string — agent IDs should be non-empty to avoid lookup ambiguity");
}
Self(id)
}
pub fn try_new(id: impl Into<String>) -> Result<Self, crate::error::AgentRuntimeError> {
let id = id.into();
if id.is_empty() {
return Err(crate::error::AgentRuntimeError::Memory(
"AgentId must not be empty".into(),
));
}
Ok(Self(id))
}
pub fn random() -> Self {
Self(Uuid::new_v4().to_string())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn starts_with(&self, prefix: &str) -> bool {
self.0.starts_with(prefix)
}
}
impl AsRef<str> for AgentId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for AgentId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for AgentId {
fn from(s: String) -> Self {
Self::new(s)
}
}
impl From<&str> for AgentId {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl std::str::FromStr for AgentId {
type Err = crate::error::AgentRuntimeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_new(s)
}
}
impl std::ops::Deref for AgentId {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct MemoryId(pub String);
impl MemoryId {
pub fn new(id: impl Into<String>) -> Self {
let id = id.into();
if id.is_empty() {
debug_assert!(false, "MemoryId must not be empty");
tracing::warn!("MemoryId::new called with an empty string — memory IDs should be non-empty to avoid lookup ambiguity");
}
Self(id)
}
pub fn try_new(id: impl Into<String>) -> Result<Self, crate::error::AgentRuntimeError> {
let id = id.into();
if id.is_empty() {
return Err(crate::error::AgentRuntimeError::Memory(
"MemoryId must not be empty".into(),
));
}
Ok(Self(id))
}
pub fn random() -> Self {
Self(Uuid::new_v4().to_string())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn starts_with(&self, prefix: &str) -> bool {
self.0.starts_with(prefix)
}
}
impl AsRef<str> for MemoryId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for MemoryId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for MemoryId {
fn from(s: String) -> Self {
Self::new(s)
}
}
impl From<&str> for MemoryId {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl std::str::FromStr for MemoryId {
type Err = crate::error::AgentRuntimeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_new(s)
}
}
impl std::ops::Deref for MemoryId {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_id_new_stores_value() {
let id = AgentId::new("agent-1");
assert_eq!(id.as_str(), "agent-1");
}
#[test]
fn test_agent_id_try_new_rejects_empty() {
assert!(AgentId::try_new("").is_err());
}
#[test]
fn test_agent_id_try_new_accepts_nonempty() {
let id = AgentId::try_new("ok").unwrap();
assert_eq!(id.as_str(), "ok");
}
#[test]
fn test_agent_id_random_generates_unique_ids() {
let a = AgentId::random();
let b = AgentId::random();
assert_ne!(a, b);
}
#[test]
fn test_agent_id_len_and_is_empty() {
let id = AgentId::new("abc");
assert_eq!(id.len(), 3);
assert!(!id.is_empty());
}
#[test]
fn test_agent_id_display() {
let id = AgentId::new("my-agent");
assert_eq!(id.to_string(), "my-agent");
}
#[test]
fn test_memory_id_new_stores_value() {
let id = MemoryId::new("mem-42");
assert_eq!(id.as_str(), "mem-42");
}
#[test]
fn test_memory_id_try_new_rejects_empty() {
assert!(MemoryId::try_new("").is_err());
}
#[test]
fn test_memory_id_len_and_is_empty() {
let id = MemoryId::new("hello");
assert_eq!(id.len(), 5);
assert!(!id.is_empty());
}
#[test]
fn test_memory_id_display() {
let id = MemoryId::new("mem-id");
assert_eq!(id.to_string(), "mem-id");
}
#[test]
fn test_memory_id_random_generates_unique_ids() {
let a = MemoryId::random();
let b = MemoryId::random();
assert_ne!(a, b);
}
#[test]
fn test_agent_id_starts_with_matching_prefix() {
let id = AgentId::new("agent-001");
assert!(id.starts_with("agent-"));
assert!(!id.starts_with("user-"));
}
#[test]
fn test_agent_id_starts_with_empty_prefix_always_true() {
let id = AgentId::new("anything");
assert!(id.starts_with(""));
}
#[test]
fn test_memory_id_starts_with_matching_prefix() {
let id = MemoryId::new("mem-42");
assert!(id.starts_with("mem-"));
assert!(!id.starts_with("ep-"));
}
#[test]
fn test_agent_id_ord_allows_sorting() {
let mut ids = vec![AgentId::new("c"), AgentId::new("a"), AgentId::new("b")];
ids.sort();
assert_eq!(ids[0].as_str(), "a");
assert_eq!(ids[2].as_str(), "c");
}
#[test]
fn test_agent_id_ord_allows_btreemap_key() {
use std::collections::BTreeMap;
let mut map: BTreeMap<AgentId, u32> = BTreeMap::new();
map.insert(AgentId::new("agent-2"), 2);
map.insert(AgentId::new("agent-1"), 1);
let keys: Vec<_> = map.keys().map(|k| k.as_str()).collect();
assert_eq!(keys, vec!["agent-1", "agent-2"]);
}
#[test]
fn test_agent_id_from_string() {
let id = AgentId::from("my-agent".to_owned());
assert_eq!(id.as_str(), "my-agent");
}
#[test]
fn test_agent_id_from_str_ref() {
let id = AgentId::from("my-agent");
assert_eq!(id.as_str(), "my-agent");
}
#[test]
fn test_agent_id_from_str_parse_rejects_empty() {
let result: Result<AgentId, _> = "".parse();
assert!(result.is_err());
}
#[test]
fn test_agent_id_from_str_parse_accepts_nonempty() {
let id: AgentId = "worker-1".parse().unwrap();
assert_eq!(id.as_str(), "worker-1");
}
#[test]
fn test_agent_id_deref_to_str() {
let id = AgentId::new("deref-test");
let s: &str = &id;
assert_eq!(s, "deref-test");
}
#[test]
fn test_agent_id_deref_enables_str_methods() {
let id = AgentId::new("hello-world");
assert!(id.contains('-'));
assert_eq!(id.len(), 11);
}
#[test]
fn test_memory_id_ord_allows_sorting() {
let mut ids = vec![MemoryId::new("z"), MemoryId::new("a"), MemoryId::new("m")];
ids.sort();
assert_eq!(ids[0].as_str(), "a");
assert_eq!(ids[2].as_str(), "z");
}
#[test]
fn test_memory_id_from_string() {
let id = MemoryId::from("mem-x".to_owned());
assert_eq!(id.as_str(), "mem-x");
}
#[test]
fn test_memory_id_from_str_ref() {
let id = MemoryId::from("mem-y");
assert_eq!(id.as_str(), "mem-y");
}
#[test]
fn test_memory_id_from_str_parse_rejects_empty() {
let result: Result<MemoryId, _> = "".parse();
assert!(result.is_err());
}
#[test]
fn test_memory_id_from_str_parse_accepts_nonempty() {
let id: MemoryId = "ep-001".parse().unwrap();
assert_eq!(id.as_str(), "ep-001");
}
#[test]
fn test_memory_id_deref_to_str() {
let id = MemoryId::new("deref-mem");
let s: &str = &id;
assert_eq!(s, "deref-mem");
}
}