use crate::message::Message;
pub trait ContextContributor: Send + Sync {
fn contribute(&self, ctx: &ContributorContext<'_>) -> Option<Message>;
}
#[derive(Debug)]
pub struct ContributorContext<'a> {
pub turn: usize,
pub conversation: &'a [Message],
}
impl<'a> ContributorContext<'a> {
#[must_use]
pub fn new(turn: usize, conversation: &'a [Message]) -> Self {
Self { turn, conversation }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::message::{Message, MessagePart, Role};
#[test]
fn test_contributor_context_new_round_trip() {
let msgs = vec![Message::user("hello"), Message::assistant("hi")];
let ctx = ContributorContext::new(3, &msgs);
assert_eq!(ctx.turn, 3);
assert_eq!(ctx.conversation.len(), 2);
assert_eq!(ctx.conversation.as_ptr(), msgs.as_ptr());
assert_eq!(ctx.conversation.len(), msgs.len());
}
#[test]
fn test_contributor_context_debug_formats() {
let msgs = vec![Message::user("hello")];
let ctx = ContributorContext::new(7, &msgs);
let s = format!("{ctx:?}");
assert!(s.contains("turn"), "Debug output should mention turn: {s}");
assert!(
s.contains('7'),
"Debug output should mention turn number: {s}"
);
}
#[test]
fn test_contributor_context_empty_conversation() {
let msgs: Vec<Message> = Vec::new();
let ctx = ContributorContext::new(0, &msgs);
assert_eq!(ctx.turn, 0);
assert!(ctx.conversation.is_empty());
}
#[test]
fn test_contributor_trait_object_safe() {
struct Always;
impl ContextContributor for Always {
fn contribute(&self, _ctx: &ContributorContext<'_>) -> Option<Message> {
Some(Message::new(
Role::System,
vec![MessagePart::text("stay on task")],
))
}
}
let boxed: Box<dyn ContextContributor> = Box::new(Always);
let msgs = vec![Message::user("go")];
let ctx = ContributorContext::new(1, &msgs);
let out = boxed.contribute(&ctx).expect("Always returns Some");
assert_eq!(out.role, Role::System);
assert_eq!(out.parts.len(), 1);
}
}