use crate::wire::AmpMemoryType;
#[derive(Debug, Clone, PartialEq)]
pub struct WriteDiff {
pub agent_id: String,
pub memory_type: AmpMemoryType,
pub before: Option<String>,
pub after: String,
pub tags: Vec<String>,
}
impl WriteDiff {
pub fn render(&self) -> String {
match &self.before {
Some(b) => format!(
"[{}] tags={:?}\n- {}\n+ {}",
self.memory_type.as_str(),
self.tags,
b,
self.after
),
None => format!(
"[{}] tags={:?}\n+ {}",
self.memory_type.as_str(),
self.tags,
self.after
),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Approval {
Approve,
Reject(String),
}
impl Approval {
pub fn is_approved(&self) -> bool {
matches!(self, Approval::Approve)
}
}
pub trait ApprovalHook: Send + Sync {
fn review(&self, diff: &WriteDiff) -> Approval;
fn name(&self) -> &str;
}
#[derive(Debug, Clone, Default)]
pub struct AutoApprove;
impl ApprovalHook for AutoApprove {
fn review(&self, _diff: &WriteDiff) -> Approval {
Approval::Approve
}
fn name(&self) -> &str {
"auto_approve"
}
}
pub struct ClosureApprove {
f: Box<dyn Fn(&WriteDiff) -> Approval + Send + Sync>,
}
impl ClosureApprove {
pub fn new<F>(f: F) -> Self
where
F: Fn(&WriteDiff) -> Approval + Send + Sync + 'static,
{
Self { f: Box::new(f) }
}
}
impl ApprovalHook for ClosureApprove {
fn review(&self, diff: &WriteDiff) -> Approval {
(self.f)(diff)
}
fn name(&self) -> &str {
"closure_approve"
}
}
#[cfg(test)]
mod tests {
use super::*;
fn diff() -> WriteDiff {
WriteDiff {
agent_id: "a".into(),
memory_type: AmpMemoryType::Semantic,
before: None,
after: "Paris is the capital of France".into(),
tags: vec!["geo".into()],
}
}
#[test]
fn auto_approve_always_approves() {
assert_eq!(AutoApprove.review(&diff()), Approval::Approve);
}
#[test]
fn closure_hook_is_honoured() {
let hook = ClosureApprove::new(|d| {
if d.after.contains("France") {
Approval::Approve
} else {
Approval::Reject("off-topic".into())
}
});
assert!(hook.review(&diff()).is_approved());
let mut other = diff();
other.after = "unrelated".into();
assert_eq!(hook.review(&other), Approval::Reject("off-topic".into()));
}
#[test]
fn diff_render_is_deterministic() {
let d = diff();
assert_eq!(d.render(), d.render());
assert!(d.render().contains("+ Paris is the capital of France"));
}
}