tiny_agent/trajectory/
mod.rs1use crate::shared::{ContentBlock, UserInteraction};
12use async_trait::async_trait;
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15use std::sync::Arc;
16use std::time::{SystemTime, UNIX_EPOCH};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct TrajectoryEvent {
21 pub session_id: String,
22 pub at: u64,
23 #[serde(flatten)]
24 pub kind: TrajectoryEventKind,
25}
26
27impl TrajectoryEvent {
28 pub fn new(session_id: &str, kind: TrajectoryEventKind) -> Self {
29 let at = SystemTime::now()
30 .duration_since(UNIX_EPOCH)
31 .map(|d| d.as_millis() as u64)
32 .unwrap_or(0);
33 Self {
34 session_id: session_id.to_string(),
35 at,
36 kind,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(tag = "type", rename_all = "snake_case")]
44pub enum TrajectoryEventKind {
45 UserMessage { text: String },
47 ReasoningStarted { request: Value },
50 AssistantMessage { blocks: Vec<ContentBlock> },
52 ToolStarted {
54 call_id: String,
55 name: String,
56 arguments: Value,
57 },
58 ToolCompleted {
60 call_id: String,
61 name: String,
62 output: Vec<ContentBlock>,
63 is_error: bool,
64 },
65 Interrupted,
67 WaitingForUser { interaction: UserInteraction },
69 Failed { kind: String, message: String },
71 Succeeded { final_resp: Option<String> },
73}
74
75impl TrajectoryEventKind {
76 pub fn kind_type(&self) -> &'static str {
77 match self {
78 Self::UserMessage { .. } => "user_message",
79 Self::ReasoningStarted { .. } => "reasoning_started",
80 Self::AssistantMessage { .. } => "assistant_message",
81 Self::ToolStarted { .. } => "tool_started",
82 Self::ToolCompleted { .. } => "tool_completed",
83 Self::Interrupted => "interrupted",
84 Self::WaitingForUser { .. } => "waiting_for_user",
85 Self::Failed { .. } => "failed",
86 Self::Succeeded { .. } => "succeeded",
87 }
88 }
89}
90
91#[async_trait]
95pub trait TrajectorySink: Send + Sync {
96 async fn emit(&self, event: TrajectoryEvent);
97}
98
99pub struct NoopSink;
101
102#[async_trait]
103impl TrajectorySink for NoopSink {
104 async fn emit(&self, _event: TrajectoryEvent) {}
105}
106
107pub struct ChannelSink {
110 tx: tokio::sync::mpsc::UnboundedSender<TrajectoryEvent>,
111}
112
113impl ChannelSink {
114 pub fn new() -> (
116 Arc<Self>,
117 tokio::sync::mpsc::UnboundedReceiver<TrajectoryEvent>,
118 ) {
119 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
120 (Arc::new(Self { tx }), rx)
121 }
122}
123
124#[async_trait]
125impl TrajectorySink for ChannelSink {
126 async fn emit(&self, event: TrajectoryEvent) {
127 let _ = self.tx.send(event);
129 }
130}