use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::driver::ModelSpec;
use crate::error::Result;
use crate::event::EventData;
use crate::event_log::{EventStore, SessionPoint};
use crate::id::TurnId;
use crate::message::Message;
pub struct ContextRequest<'a> {
pub point: SessionPoint,
pub turn_id: TurnId,
pub iteration: usize,
pub model: &'a ModelSpec,
pub token_limit: Option<usize>,
pub messages: &'a [Message],
pub events: &'a dyn EventStore,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ContextSource {
pub kind: String,
pub from: Option<SessionPoint>,
pub through: Option<SessionPoint>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ContextEvent {
pub event_type: String,
pub payload: serde_json::Value,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ContextAssembly {
pub messages: Vec<Message>,
pub estimated_tokens: Option<usize>,
pub sources: Vec<ContextSource>,
pub events: Vec<ContextEvent>,
}
impl ContextAssembly {
pub fn new(messages: Vec<Message>) -> Self {
Self {
messages,
estimated_tokens: None,
sources: Vec::new(),
events: Vec::new(),
}
}
pub fn into_messages_and_events(self) -> (Vec<Message>, Vec<EventData>) {
let mut events = Vec::new();
if self.estimated_tokens.is_some() || !self.sources.is_empty() {
events.push(EventData::custom(
"context.assembled",
serde_json::json!({
"estimated_tokens": self.estimated_tokens,
"sources": self.sources,
}),
));
}
events.extend(
self.events
.into_iter()
.map(|event| EventData::custom(event.event_type, event.payload)),
);
(self.messages, events)
}
}
#[async_trait]
pub trait ContextAssembler: Send + Sync {
async fn assemble(&self, request: ContextRequest<'_>) -> Result<ContextAssembly>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct PassthroughContextAssembler;
#[async_trait]
impl ContextAssembler for PassthroughContextAssembler {
async fn assemble(&self, request: ContextRequest<'_>) -> Result<ContextAssembly> {
Ok(ContextAssembly {
messages: request.messages.to_vec(),
estimated_tokens: None,
sources: Vec::new(),
events: Vec::new(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn passthrough_returns_history_unchanged() {
let messages = vec![Message::user("hi"), Message::assistant("hello")];
let assembler = PassthroughContextAssembler;
use std::pin::pin;
use std::task::{Context, Poll, Waker};
let model = ModelSpec::llmsim();
let session_id = crate::id::SessionId::new();
let events = crate::event_log::InMemoryEventLog::new();
let future = assembler.assemble(ContextRequest {
point: SessionPoint::new(session_id, 2),
turn_id: TurnId::new(),
iteration: 1,
model: &model,
token_limit: None,
messages: &messages,
events: &events,
});
let mut future = pin!(future);
let waker = Waker::noop();
let mut cx = Context::from_waker(waker);
let result = loop {
if let Poll::Ready(output) = future.as_mut().poll(&mut cx) {
break output;
}
};
assert_eq!(result.unwrap().messages, messages);
}
}