1use serde::{Deserialize, Serialize};
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use crate::lsp::LspServerInfo;
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct AgentEvent {
11 pub event_type: EventType,
12 pub timestamp: u64,
13 #[serde(skip_serializing_if = "Option::is_none")]
14 pub data: Option<EventData>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
19#[serde(rename_all = "snake_case")]
20pub enum EventType {
21 TextStart,
22 TextDelta,
23 TextEnd,
24 ThinkingStart,
25 ThinkingDelta,
26 ThinkingEnd,
27 ToolUseStart,
28 ToolUseInputDelta,
29 ToolUseInputEnd,
30 ToolResult,
31 SessionStarted,
32 SessionEnded,
33 SessionRestored, NewSession,
35 CompressionTriggered,
36 CompressionCompleted,
37 MemoryLoaded,
38 MemoryDetected, KeywordsExtracted, Error,
41 Usage,
42 Progress,
43 ContextSize, AskQuestion, ProxyToolRequest, ProxyToolResponse, DebugLog, SkillsLoaded, WorkflowsLoaded, McpServerAdded, QueueProcessed, McpServerRemoved, McpServerStatus, LspServerAdded, LspServerRemoved, LspServerStatus, }
58
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
61#[serde(rename_all = "snake_case")]
62pub enum EventData {
63 Text {
64 delta: String,
65 },
66 Thinking {
67 delta: String,
68 signature: Option<String>,
69 },
70 ToolUse {
71 id: String,
72 name: String,
73 input: Option<serde_json::Value>,
74 },
75 ToolUseInput {
76 id: String,
77 delta: String,
78 },
79 ToolResult {
80 tool_use_id: String,
81 name: String,
82 detail: Option<String>,
83 content: String,
84 is_error: bool,
85 },
86 Error {
87 message: String,
88 code: Option<String>,
89 source: Option<String>,
90 },
91 Usage {
92 input_tokens: u64,
93 output_tokens: u64,
94 cache_creation_input_tokens: Option<u64>,
95 cache_read_input_tokens: Option<u64>,
96 },
97 SessionRestore {
98 input_tokens: u64,
99 total_output_tokens: u64,
100 message_count: usize,
101 },
102 Progress {
103 message: String,
104 percentage: Option<u8>,
105 },
106 ContextSize {
107 context_size: u64,
108 },
109 Compression {
110 original_tokens: u64,
111 compressed_tokens: u64,
112 ratio: f32,
113 },
114 Memory {
115 summary: String,
116 entries_count: usize,
117 },
118 Keywords {
119 keywords: Vec<String>,
120 source: String,
121 }, AskQuestion {
123 question: String,
124 options: Option<serde_json::Value>,
125 },
126 ProxyToolRequest {
128 request_id: String,
129 tool_name: String,
130 tool_input: serde_json::Value,
131 metadata: crate::tools::toolproxy::ProxyMetadata,
132 },
133 ProxyToolResponse {
135 request_id: String,
136 result: String,
137 is_error: bool,
138 },
139 DebugLog {
140 category: String,
141 message: String,
142 }, SkillsLoaded {
144 names: Vec<String>,
145 },
146 WorkflowsLoaded {
147 names: Vec<String>,
148 },
149 McpServerAdded {
150 name: String,
151 tool_count: usize,
152 },
153 QueueProcessed {
154 count: usize,
155 messages: Vec<String>, }, McpServerRemoved {
158 name: String,
159 },
160 McpServerStatus {
161 servers: Vec<McpServerInfo>,
162 },
163 LspServerAdded {
164 name: String,
165 language: String,
166 },
167 LspServerRemoved {
168 name: String,
169 },
170 LspServerStatus {
171 servers: Vec<LspServerInfo>,
172 },
173}
174
175impl AgentEvent {
176 pub fn new(event_type: EventType) -> Self {
177 Self {
178 event_type,
179 timestamp: current_timestamp(),
180 data: None,
181 }
182 }
183
184 pub fn with_data(event_type: EventType, data: EventData) -> Self {
185 Self {
186 event_type,
187 timestamp: current_timestamp(),
188 data: Some(data),
189 }
190 }
191
192 pub fn text_delta(delta: impl Into<String>) -> Self {
193 Self::with_data(
194 EventType::TextDelta,
195 EventData::Text {
196 delta: delta.into(),
197 },
198 )
199 }
200
201 pub fn text_start() -> Self {
202 Self::new(EventType::TextStart)
203 }
204 pub fn text_end() -> Self {
205 Self::new(EventType::TextEnd)
206 }
207 pub fn thinking_start() -> Self {
208 Self::new(EventType::ThinkingStart)
209 }
210 pub fn thinking_end() -> Self {
211 Self::new(EventType::ThinkingEnd)
212 }
213 pub fn session_started() -> Self {
214 Self::new(EventType::SessionStarted)
215 }
216 pub fn session_ended() -> Self {
217 Self::new(EventType::SessionEnded)
218 }
219 pub fn session_restored(
220 input_tokens: u64,
221 total_output_tokens: u64,
222 message_count: usize,
223 ) -> Self {
224 Self::with_data(
225 EventType::SessionRestored,
226 EventData::SessionRestore {
227 input_tokens,
228 total_output_tokens,
229 message_count,
230 },
231 )
232 }
233
234 pub fn thinking_delta(delta: impl Into<String>, signature: Option<String>) -> Self {
235 Self::with_data(
236 EventType::ThinkingDelta,
237 EventData::Thinking {
238 delta: delta.into(),
239 signature,
240 },
241 )
242 }
243
244 pub fn tool_use_start(
245 id: impl Into<String>,
246 name: impl Into<String>,
247 input: Option<serde_json::Value>,
248 ) -> Self {
249 Self::with_data(
250 EventType::ToolUseStart,
251 EventData::ToolUse {
252 id: id.into(),
253 name: name.into(),
254 input,
255 },
256 )
257 }
258
259 pub fn tool_result(
260 tool_use_id: impl Into<String>,
261 name: impl Into<String>,
262 detail: Option<String>,
263 content: impl Into<String>,
264 is_error: bool,
265 ) -> Self {
266 Self::with_data(
267 EventType::ToolResult,
268 EventData::ToolResult {
269 tool_use_id: tool_use_id.into(),
270 name: name.into(),
271 detail,
272 content: content.into(),
273 is_error,
274 },
275 )
276 }
277
278 pub fn error(message: impl Into<String>, code: Option<String>, source: Option<String>) -> Self {
279 Self::with_data(
280 EventType::Error,
281 EventData::Error {
282 message: message.into(),
283 code,
284 source,
285 },
286 )
287 }
288
289 pub fn progress(message: impl Into<String>, percentage: Option<u8>) -> Self {
290 Self::with_data(
291 EventType::Progress,
292 EventData::Progress {
293 message: message.into(),
294 percentage,
295 },
296 )
297 }
298
299 pub fn queue_processed(count: usize, messages: Vec<String>) -> Self {
300 Self::with_data(
301 EventType::QueueProcessed,
302 EventData::QueueProcessed { count, messages },
303 )
304 }
305
306 pub fn usage(input_tokens: u64, output_tokens: u64) -> Self {
307 Self::with_data(
308 EventType::Usage,
309 EventData::Usage {
310 input_tokens,
311 output_tokens,
312 cache_creation_input_tokens: None,
313 cache_read_input_tokens: None,
314 },
315 )
316 }
317
318 pub fn usage_with_cache(
319 input_tokens: u64,
320 output_tokens: u64,
321 cache_read: u64,
322 cache_created: u64,
323 ) -> Self {
324 Self::with_data(
325 EventType::Usage,
326 EventData::Usage {
327 input_tokens,
328 output_tokens,
329 cache_creation_input_tokens: if cache_created > 0 {
330 Some(cache_created)
331 } else {
332 None
333 },
334 cache_read_input_tokens: if cache_read > 0 {
335 Some(cache_read)
336 } else {
337 None
338 },
339 },
340 )
341 }
342
343 pub fn debug_log(category: impl Into<String>, message: impl Into<String>) -> Self {
344 Self::with_data(
345 EventType::DebugLog,
346 EventData::DebugLog {
347 category: category.into(),
348 message: message.into(),
349 },
350 )
351 }
352
353 pub fn skills_loaded(names: Vec<String>) -> Self {
354 Self::with_data(EventType::SkillsLoaded, EventData::SkillsLoaded { names })
355 }
356
357 pub fn workflows_loaded(names: Vec<String>) -> Self {
358 Self::with_data(
359 EventType::WorkflowsLoaded,
360 EventData::WorkflowsLoaded { names },
361 )
362 }
363
364 pub fn mcp_server_added(name: impl Into<String>, tool_count: usize) -> Self {
366 Self::with_data(
367 EventType::McpServerAdded,
368 EventData::McpServerAdded {
369 name: name.into(),
370 tool_count,
371 },
372 )
373 }
374
375 pub fn mcp_server_removed(name: impl Into<String>) -> Self {
377 Self::with_data(
378 EventType::McpServerRemoved,
379 EventData::McpServerRemoved { name: name.into() },
380 )
381 }
382
383 pub fn mcp_server_status(servers: Vec<McpServerInfo>) -> Self {
385 Self::with_data(
386 EventType::McpServerStatus,
387 EventData::McpServerStatus { servers },
388 )
389 }
390
391 pub fn lsp_server_added(name: impl Into<String>, language: impl Into<String>) -> Self {
393 Self::with_data(
394 EventType::LspServerAdded,
395 EventData::LspServerAdded {
396 name: name.into(),
397 language: language.into(),
398 },
399 )
400 }
401
402 pub fn lsp_server_removed(name: impl Into<String>) -> Self {
404 Self::with_data(
405 EventType::LspServerRemoved,
406 EventData::LspServerRemoved { name: name.into() },
407 )
408 }
409
410 pub fn lsp_server_status(servers: Vec<LspServerInfo>) -> Self {
412 Self::with_data(
413 EventType::LspServerStatus,
414 EventData::LspServerStatus { servers },
415 )
416 }
417
418 pub fn proxy_tool_request(
420 request_id: impl Into<String>,
421 tool_name: impl Into<String>,
422 tool_input: serde_json::Value,
423 metadata: crate::tools::toolproxy::ProxyMetadata,
424 ) -> Self {
425 Self::with_data(
426 EventType::ProxyToolRequest,
427 EventData::ProxyToolRequest {
428 request_id: request_id.into(),
429 tool_name: tool_name.into(),
430 tool_input,
431 metadata,
432 },
433 )
434 }
435
436 pub fn proxy_tool_response(
438 request_id: impl Into<String>,
439 result: impl Into<String>,
440 is_error: bool,
441 ) -> Self {
442 Self::with_data(
443 EventType::ProxyToolResponse,
444 EventData::ProxyToolResponse {
445 request_id: request_id.into(),
446 result: result.into(),
447 is_error,
448 },
449 )
450 }
451
452 pub fn to_json(&self) -> Result<String, serde_json::Error> {
453 serde_json::to_string(self)
454 }
455 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
456 serde_json::from_str(json)
457 }
458}
459
460fn current_timestamp() -> u64 {
461 SystemTime::now()
462 .duration_since(UNIX_EPOCH)
463 .unwrap_or_default()
464 .as_millis() as u64
465}
466
467#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
469pub struct McpServerInfo {
470 pub name: String,
471 pub is_started: bool,
472 pub tool_count: usize,
473}
474
475impl McpServerInfo {
476 pub fn new(name: impl Into<String>, is_started: bool, tool_count: usize) -> Self {
477 Self {
478 name: name.into(),
479 is_started,
480 tool_count,
481 }
482 }
483
484 pub fn from_status(status: &crate::mcp::ServerStatus) -> Self {
485 Self {
486 name: status.name.clone(),
487 is_started: status.is_started,
488 tool_count: status.tool_count,
489 }
490 }
491}
492
493#[derive(Debug, Default)]
494pub struct EventCollector {
495 events: Vec<AgentEvent>,
496}
497
498impl EventCollector {
499 pub fn new() -> Self {
500 Self::default()
501 }
502 pub fn push(&mut self, event: AgentEvent) {
503 self.events.push(event);
504 }
505 pub fn events(&self) -> &[AgentEvent] {
506 &self.events
507 }
508 pub fn len(&self) -> usize {
509 self.events.len()
510 }
511 pub fn is_empty(&self) -> bool {
512 self.events.is_empty()
513 }
514 pub fn clear(&mut self) {
515 self.events.clear();
516 }
517 pub fn to_json_lines(&self) -> Result<Vec<String>, serde_json::Error> {
518 self.events.iter().map(|e| e.to_json()).collect()
519 }
520 pub fn output_json_lines(&self) -> Result<String, serde_json::Error> {
521 Ok(self.to_json_lines()?.join("\n"))
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528 #[test]
529 fn test_event() {
530 let e = AgentEvent::text_delta("Hello");
531 assert!(e.to_json().unwrap().contains("Hello"));
532 }
533}