use std::sync::Arc;
use crate::agent_builder::AgentBuilder;
use crate::observability::{AuditLog, CostTracker, Tracer};
use crate::security::Authorizer;
pub trait AgentDecorator: Send + Sync {
fn decorate<'a>(&self, builder: AgentBuilder<'a>) -> AgentBuilder<'a>;
}
#[derive(Default, Clone)]
pub struct ObservabilityDecorator {
audit: Option<Arc<AuditLog>>,
authorizer: Option<Arc<Authorizer>>,
tracer: Option<Arc<Tracer>>,
cost_tracker: Option<Arc<CostTracker>>,
}
impl ObservabilityDecorator {
pub fn new() -> Self {
Self::default()
}
pub fn with_audit(mut self, audit: Arc<AuditLog>) -> Self {
self.audit = Some(audit);
self
}
pub fn with_authorizer(mut self, authorizer: Arc<Authorizer>) -> Self {
self.authorizer = Some(authorizer);
self
}
pub fn with_tracer(mut self, tracer: Arc<Tracer>) -> Self {
self.tracer = Some(tracer);
self
}
pub fn with_cost_tracker(mut self, tracker: Arc<CostTracker>) -> Self {
self.cost_tracker = Some(tracker);
self
}
pub fn audit(&self) -> Option<&Arc<AuditLog>> {
self.audit.as_ref()
}
pub fn authorizer(&self) -> Option<&Arc<Authorizer>> {
self.authorizer.as_ref()
}
pub fn tracer(&self) -> Option<&Arc<Tracer>> {
self.tracer.as_ref()
}
pub fn cost_tracker(&self) -> Option<&Arc<CostTracker>> {
self.cost_tracker.as_ref()
}
}
impl AgentDecorator for ObservabilityDecorator {
fn decorate<'a>(&self, builder: AgentBuilder<'a>) -> AgentBuilder<'a> {
let b = builder;
let b = if let Some(a) = self.audit.as_ref() {
b.audit_log(a.clone())
} else {
b
};
let b = if let Some(a) = self.authorizer.as_ref() {
b.authorizer(a.clone())
} else {
b
};
let b = if let Some(t) = self.tracer.as_ref() {
b.tracer(t.clone())
} else {
b
};
if let Some(c) = self.cost_tracker.as_ref() {
b.cost_tracker(c.clone())
} else {
b
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::observability::{AuditLog, CostTrackerConfig, Tracer};
use crate::security::Authorizer;
use oxi_ai::ModelRegistry;
#[test]
fn empty_decorator_is_noop_on_fields() {
let d = ObservabilityDecorator::new();
assert!(d.audit().is_none());
assert!(d.authorizer().is_none());
assert!(d.tracer().is_none());
assert!(d.cost_tracker().is_none());
}
#[test]
fn builders_set_fields() {
let audit = Arc::new(AuditLog::new(64));
let tracer = Arc::new(Tracer::new());
let authorizer = Arc::new(Authorizer::new(audit.clone()));
let registry = Arc::new(ModelRegistry::new());
let cost = Arc::new(CostTracker::new(registry, CostTrackerConfig::default()));
let d = ObservabilityDecorator::new()
.with_audit(audit.clone())
.with_tracer(tracer.clone())
.with_authorizer(authorizer.clone())
.with_cost_tracker(cost.clone());
assert!(Arc::ptr_eq(d.audit().unwrap(), &audit));
assert!(Arc::ptr_eq(d.tracer().unwrap(), &tracer));
assert!(Arc::ptr_eq(d.authorizer().unwrap(), &authorizer));
assert!(Arc::ptr_eq(d.cost_tracker().unwrap(), &cost));
}
#[test]
fn decorator_implements_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<ObservabilityDecorator>();
assert_send_sync::<Arc<dyn AgentDecorator>>();
}
}