1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::core::{MessageRole, ToolCall, ToolResponse};
5use crate::hooks::InlineHookRequest;
6
7#[derive(Debug, Serialize, Deserialize, Clone)]
8#[serde(rename_all = "snake_case")]
9pub struct AgentEvent {
10 pub timestamp: chrono::DateTime<chrono::Utc>,
11 pub thread_id: String,
12 pub run_id: String,
13 pub event: AgentEventType,
14 pub task_id: String,
15 pub agent_id: String,
16}
17
18#[derive(Debug, Serialize, Deserialize, Clone)]
19#[serde(rename_all = "snake_case", tag = "type")]
20pub enum AgentEventType {
21 RunStarted {},
23 RunFinished {
24 success: bool,
25 total_steps: usize,
26 failed_steps: usize,
27 },
28 RunError {
29 message: String,
30 code: Option<String>,
31 },
32 PlanStarted {
33 initial_plan: bool,
34 },
35 PlanFinished {
36 total_steps: usize,
37 },
38 PlanPruned {
39 removed_steps: Vec<String>,
40 },
41 StepStarted {
43 step_id: String,
44 step_index: usize,
45 },
46 StepCompleted {
47 step_id: String,
48 success: bool,
49 },
50
51 ToolExecutionStart {
53 step_id: String,
54 tool_call_id: String,
55 tool_call_name: String,
56 input: Value,
57 },
58 ToolExecutionEnd {
59 step_id: String,
60 tool_call_id: String,
61 tool_call_name: String,
62 success: bool,
63 },
64
65 TextMessageStart {
67 message_id: String,
68 step_id: String,
69 role: MessageRole,
70 is_final: Option<bool>,
71 },
72 TextMessageContent {
73 message_id: String,
74 step_id: String,
75 delta: String,
76 stripped_content: Option<Vec<(usize, String)>>,
77 },
78 TextMessageEnd {
79 message_id: String,
80 step_id: String,
81 },
82
83 ToolCalls {
85 step_id: String,
86 parent_message_id: Option<String>,
87 tool_calls: Vec<ToolCall>,
88 },
89 ToolResults {
90 step_id: String,
91 parent_message_id: Option<String>,
92 results: Vec<ToolResponse>,
93 },
94
95 AgentHandover {
97 from_agent: String,
98 to_agent: String,
99 reason: Option<String>,
100 },
101
102 WorkflowStarted {
104 workflow_name: String,
105 total_steps: usize,
106 },
107 NodeStarted {
108 node_id: String,
109 node_name: String,
110 step_type: String,
111 },
112 NodeCompleted {
113 node_id: String,
114 node_name: String,
115 success: bool,
116 error: Option<String>,
117 },
118 RunCompleted {
119 workflow_name: String,
120 success: bool,
121 total_steps: usize,
122 },
123 RunFailed {
124 workflow_name: String,
125 error: String,
126 failed_at_step: Option<String>,
127 },
128
129 BrowserScreenshot {
130 image: String,
131 format: Option<String>,
132 filename: Option<String>,
133 size: Option<u64>,
134 timestamp_ms: Option<i64>,
135 },
136
137 BrowserSessionStarted {
138 session_id: String,
139 viewer_url: Option<String>,
140 stream_url: Option<String>,
141 },
142
143 InlineHookRequested {
144 request: InlineHookRequest,
145 },
146
147 TodosUpdated {
149 formatted_todos: String,
150 action: String,
151 todo_count: usize,
152 },
153}
154
155impl AgentEvent {
156 pub fn new(event: AgentEventType) -> Self {
157 Self {
158 timestamp: chrono::Utc::now(),
159 thread_id: uuid::Uuid::new_v4().to_string(),
160 run_id: uuid::Uuid::new_v4().to_string(),
161 event,
162 task_id: uuid::Uuid::new_v4().to_string(),
163 agent_id: "default".to_string(),
164 }
165 }
166
167 pub fn with_context(
168 event: AgentEventType,
169 thread_id: String,
170 run_id: String,
171 task_id: String,
172 agent_id: String,
173 ) -> Self {
174 Self {
175 timestamp: chrono::Utc::now(),
176 thread_id,
177 run_id,
178 task_id,
179 event,
180 agent_id,
181 }
182 }
183}