use serde::{Deserialize, Serialize};
use super::identifiers::{InputId, RunId};
use super::run_receipt::RunBoundaryReceipt;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "event_type", rename_all = "snake_case")]
pub enum RunEvent {
RunStarted {
run_id: RunId,
},
BoundaryApplied {
run_id: RunId,
receipt: RunBoundaryReceipt,
#[serde(default, skip_serializing_if = "Option::is_none")]
session_snapshot: Option<Vec<u8>>,
},
RunCompleted {
run_id: RunId,
consumed_input_ids: Vec<InputId>,
},
RunFailed {
run_id: RunId,
error: String,
recoverable: bool,
},
RunCancelled {
run_id: RunId,
},
}
impl RunEvent {
pub fn run_id(&self) -> &RunId {
match self {
RunEvent::RunStarted { run_id }
| RunEvent::BoundaryApplied { run_id, .. }
| RunEvent::RunCompleted { run_id, .. }
| RunEvent::RunFailed { run_id, .. }
| RunEvent::RunCancelled { run_id } => run_id,
}
}
pub fn contributing_input_ids(&self) -> &[InputId] {
match self {
RunEvent::BoundaryApplied { receipt, .. } => &receipt.contributing_input_ids,
RunEvent::RunCompleted {
consumed_input_ids, ..
} => consumed_input_ids,
_ => &[],
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::lifecycle::run_primitive::RunApplyBoundary;
#[test]
fn run_started_serde() {
let event = RunEvent::RunStarted {
run_id: RunId::new(),
};
let json = serde_json::to_value(&event).unwrap();
assert_eq!(json["event_type"], "run_started");
let parsed: RunEvent = serde_json::from_value(json).unwrap();
assert_eq!(event.run_id(), parsed.run_id());
}
#[test]
fn boundary_applied_serde() {
let receipt = RunBoundaryReceipt {
run_id: RunId::new(),
boundary: RunApplyBoundary::RunStart,
contributing_input_ids: vec![InputId::new()],
conversation_digest: Some("digest".into()),
message_count: 3,
sequence: 0,
};
let event = RunEvent::BoundaryApplied {
run_id: receipt.run_id.clone(),
receipt: receipt.clone(),
session_snapshot: Some(b"session".to_vec()),
};
let json = serde_json::to_value(&event).unwrap();
assert_eq!(json["event_type"], "boundary_applied");
let parsed: RunEvent = serde_json::from_value(json).unwrap();
assert_eq!(
parsed.contributing_input_ids(),
receipt.contributing_input_ids.as_slice()
);
}
#[test]
fn run_completed_serde() {
let ids = vec![InputId::new(), InputId::new()];
let event = RunEvent::RunCompleted {
run_id: RunId::new(),
consumed_input_ids: ids.clone(),
};
let json = serde_json::to_value(&event).unwrap();
assert_eq!(json["event_type"], "run_completed");
let parsed: RunEvent = serde_json::from_value(json).unwrap();
assert_eq!(parsed.contributing_input_ids(), ids.as_slice());
}
#[test]
fn run_failed_serde() {
let event = RunEvent::RunFailed {
run_id: RunId::new(),
error: "LLM timeout".into(),
recoverable: true,
};
let json = serde_json::to_value(&event).unwrap();
assert_eq!(json["event_type"], "run_failed");
assert_eq!(json["recoverable"], true);
let parsed: RunEvent = serde_json::from_value(json).unwrap();
assert!(matches!(
parsed,
RunEvent::RunFailed {
recoverable: true,
..
}
));
}
#[test]
fn run_cancelled_serde() {
let event = RunEvent::RunCancelled {
run_id: RunId::new(),
};
let json = serde_json::to_value(&event).unwrap();
assert_eq!(json["event_type"], "run_cancelled");
let parsed: RunEvent = serde_json::from_value(json).unwrap();
assert!(matches!(parsed, RunEvent::RunCancelled { .. }));
}
#[test]
fn run_event_run_id_accessor() {
let run_id = RunId::new();
let events = vec![
RunEvent::RunStarted {
run_id: run_id.clone(),
},
RunEvent::RunCompleted {
run_id: run_id.clone(),
consumed_input_ids: vec![],
},
RunEvent::RunFailed {
run_id: run_id.clone(),
error: "err".into(),
recoverable: false,
},
RunEvent::RunCancelled {
run_id: run_id.clone(),
},
];
for event in &events {
assert_eq!(event.run_id(), &run_id);
}
}
#[test]
fn contributing_input_ids_empty_for_non_carrying_events() {
let event = RunEvent::RunStarted {
run_id: RunId::new(),
};
assert!(event.contributing_input_ids().is_empty());
let event = RunEvent::RunFailed {
run_id: RunId::new(),
error: "err".into(),
recoverable: false,
};
assert!(event.contributing_input_ids().is_empty());
let event = RunEvent::RunCancelled {
run_id: RunId::new(),
};
assert!(event.contributing_input_ids().is_empty());
}
}