use async_trait::async_trait;
use serde_json::Value;
#[derive(Debug, Clone)]
pub struct LoopEvent {
pub sequence: i64,
pub kind: LoopEventKind,
pub data: Value,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoopEventKind {
Created,
OutputItemAdded,
OutputTextDelta,
FunctionCallArgumentsDelta,
OutputItemDone,
Completed,
Failed,
}
impl LoopEventKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::Created => "response.created",
Self::OutputItemAdded => "response.output_item.added",
Self::OutputTextDelta => "response.output_text.delta",
Self::FunctionCallArgumentsDelta => "response.function_call_arguments.delta",
Self::OutputItemDone => "response.output_item.done",
Self::Completed => "response.completed",
Self::Failed => "response.failed",
}
}
}
#[async_trait]
pub trait EventSink: Send + Sync {
async fn emit(&self, event: LoopEvent) -> Result<(), EventSinkError>;
}
#[derive(Debug, Clone)]
pub struct EventSinkError(pub String);
impl std::fmt::Display for EventSinkError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "event sink error: {}", self.0)
}
}
impl std::error::Error for EventSinkError {}
pub(crate) async fn try_emit(sink: &dyn EventSink, event: LoopEvent) {
if let Err(e) = sink.emit(event).await {
tracing::debug!(error = %e, "event sink emit failed; continuing");
}
}
#[cfg(test)]
#[derive(Default)]
pub struct RecordingSink {
events: std::sync::Mutex<Vec<LoopEvent>>,
}
#[cfg(test)]
impl RecordingSink {
pub fn new() -> Self {
Self::default()
}
pub fn events(&self) -> Vec<LoopEvent> {
self.events.lock().unwrap().clone()
}
}
#[cfg(test)]
#[async_trait]
impl EventSink for RecordingSink {
async fn emit(&self, event: LoopEvent) -> Result<(), EventSinkError> {
self.events.lock().unwrap().push(event);
Ok(())
}
}