use std::{
collections::VecDeque,
sync::{Arc, Mutex},
};
use mentra::tool::ToolSideEffectLevel;
const CAPACITY: usize = 256;
#[derive(Debug, Clone, Default)]
pub struct SideEffectLevels {
entries: Arc<Mutex<VecDeque<Entry>>>,
}
#[derive(Debug)]
struct Entry {
tool_call_id: String,
level: Option<ToolSideEffectLevel>,
}
impl SideEffectLevels {
pub fn new() -> Self {
Self::default()
}
pub fn pending(&self) -> usize {
self.entries.lock().expect(POISONED).len()
}
pub(crate) fn record(&self, tool_call_id: &str, level: ToolSideEffectLevel) {
let mut entries = self.entries.lock().expect(POISONED);
if let Some(existing) = entries
.iter_mut()
.find(|entry| entry.tool_call_id == tool_call_id)
{
existing.level = None;
return;
}
if entries.len() >= CAPACITY {
entries.pop_front();
}
entries.push_back(Entry {
tool_call_id: tool_call_id.to_string(),
level: Some(level),
});
}
pub(crate) fn take(&self, tool_call_id: &str) -> Option<ToolSideEffectLevel> {
let mut entries = self.entries.lock().expect(POISONED);
let index = entries
.iter()
.position(|entry| entry.tool_call_id == tool_call_id)?;
entries.remove(index).and_then(|entry| entry.level)
}
}
const POISONED: &str = "side-effect level channel poisoned";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_recorded_level_comes_back_once_and_then_is_gone() {
let levels = SideEffectLevels::new();
levels.record("call-1", ToolSideEffectLevel::External);
assert_eq!(levels.take("call-1"), Some(ToolSideEffectLevel::External));
assert_eq!(levels.take("call-1"), None);
assert_eq!(levels.pending(), 0);
}
#[test]
fn a_level_nobody_recorded_is_unknown_rather_than_an_error() {
assert_eq!(SideEffectLevels::new().take("call-1"), None);
}
#[test]
fn two_calls_sharing_one_id_are_reported_as_unknown_rather_than_as_a_guess() {
let levels = SideEffectLevels::new();
levels.record("call-1", ToolSideEffectLevel::LocalState);
levels.record("call-1", ToolSideEffectLevel::External);
assert_eq!(levels.take("call-1"), None);
assert_eq!(levels.take("call-1"), None, "and the entry is still taken");
}
#[test]
fn levels_nobody_ever_takes_cannot_grow_without_bound() {
let levels = SideEffectLevels::new();
for index in 0..CAPACITY * 3 {
levels.record(&format!("call-{index}"), ToolSideEffectLevel::Process);
}
assert_eq!(levels.pending(), CAPACITY);
assert_eq!(
levels.take("call-0"),
None,
"the oldest stranded entry is the one evicted"
);
assert_eq!(
levels.take(&format!("call-{}", CAPACITY * 3 - 1)),
Some(ToolSideEffectLevel::Process),
"and the newest is still there to be taken"
);
}
#[test]
fn every_holder_of_the_handle_sees_one_channel() {
let levels = SideEffectLevels::new();
let forwarder = levels.clone();
levels.record("call-1", ToolSideEffectLevel::LocalState);
assert_eq!(
forwarder.take("call-1"),
Some(ToolSideEffectLevel::LocalState)
);
assert_eq!(levels.pending(), 0, "and taking from one empties both");
}
}