use crate::shared::{ContentBlock, UserInteraction};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrajectoryEvent {
pub session_id: String,
pub at: u64,
#[serde(flatten)]
pub kind: TrajectoryEventKind,
}
impl TrajectoryEvent {
pub fn new(session_id: &str, kind: TrajectoryEventKind) -> Self {
let at = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
Self {
session_id: session_id.to_string(),
at,
kind,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum TrajectoryEventKind {
UserMessage { text: String },
ReasoningStarted { request: Value },
AssistantMessage { blocks: Vec<ContentBlock> },
ToolStarted {
call_id: String,
name: String,
arguments: Value,
},
ToolCompleted {
call_id: String,
name: String,
output: Vec<ContentBlock>,
is_error: bool,
},
Interrupted,
WaitingForUser { interaction: UserInteraction },
Failed { kind: String, message: String },
Succeeded { final_resp: Option<String> },
}
impl TrajectoryEventKind {
pub fn kind_type(&self) -> &'static str {
match self {
Self::UserMessage { .. } => "user_message",
Self::ReasoningStarted { .. } => "reasoning_started",
Self::AssistantMessage { .. } => "assistant_message",
Self::ToolStarted { .. } => "tool_started",
Self::ToolCompleted { .. } => "tool_completed",
Self::Interrupted => "interrupted",
Self::WaitingForUser { .. } => "waiting_for_user",
Self::Failed { .. } => "failed",
Self::Succeeded { .. } => "succeeded",
}
}
}
#[async_trait]
pub trait TrajectorySink: Send + Sync {
async fn emit(&self, event: TrajectoryEvent);
}
pub struct NoopSink;
#[async_trait]
impl TrajectorySink for NoopSink {
async fn emit(&self, _event: TrajectoryEvent) {}
}
pub struct ChannelSink {
tx: tokio::sync::mpsc::UnboundedSender<TrajectoryEvent>,
}
impl ChannelSink {
pub fn new() -> (
Arc<Self>,
tokio::sync::mpsc::UnboundedReceiver<TrajectoryEvent>,
) {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
(Arc::new(Self { tx }), rx)
}
}
#[async_trait]
impl TrajectorySink for ChannelSink {
async fn emit(&self, event: TrajectoryEvent) {
let _ = self.tx.send(event);
}
}