1use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum HandoffSourceType {
20 Remote,
21 Local,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "lowercase")]
27pub enum SystemMessageRole {
28 System,
29 Developer,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct RepositoryInfo {
36 pub owner: String,
37 pub name: String,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub branch: Option<String>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44#[serde(rename_all = "camelCase")]
45pub struct UserMessageAttachmentItem {
46 #[serde(rename = "type")]
47 pub attachment_type: super::AttachmentType,
48 pub path: String,
49 pub display_name: String,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct ToolRequestItem {
56 pub tool_call_id: String,
57 pub name: String,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub arguments: Option<serde_json::Value>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct ToolResultContent {
65 pub content: String,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct ToolExecutionError {
71 pub message: String,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub code: Option<String>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct HookError {
79 pub message: String,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub stack: Option<String>,
82}
83
84#[derive(Debug, Clone, Default, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct SystemMessageMetadata {
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub prompt_version: Option<String>,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub variables: Option<HashMap<String, serde_json::Value>>,
92}
93
94#[derive(Debug, Clone, Default, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct SessionStartData {
102 #[serde(default)]
103 pub session_id: String,
104 #[serde(default)]
105 pub version: f64,
106 #[serde(default)]
107 pub producer: String,
108 #[serde(default)]
109 pub copilot_version: String,
110 #[serde(default)]
111 pub start_time: String,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub selected_model: Option<String>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118#[serde(rename_all = "camelCase")]
119pub struct SessionResumeData {
120 pub resume_time: String,
121 pub event_count: f64,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126#[serde(rename_all = "camelCase")]
127pub struct SessionErrorData {
128 pub error_type: String,
129 pub message: String,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 pub stack: Option<String>,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub code: Option<f64>,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub provider_call_id: Option<String>,
136}
137
138#[derive(Debug, Clone, Default, Serialize, Deserialize)]
140pub struct SessionIdleData {}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct SessionInfoData {
146 pub info_type: String,
147 pub message: String,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(rename_all = "camelCase")]
153pub struct SessionModelChangeData {
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub previous_model: Option<String>,
156 pub new_model: String,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct SessionHandoffData {
163 pub handoff_time: String,
164 pub source_type: HandoffSourceType,
165 #[serde(skip_serializing_if = "Option::is_none")]
166 pub repository: Option<RepositoryInfo>,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub context: Option<String>,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub summary: Option<String>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub remote_session_id: Option<String>,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177#[serde(rename_all = "camelCase")]
178pub struct SessionTruncationData {
179 pub token_limit: f64,
180 pub pre_truncation_tokens_in_messages: f64,
181 pub pre_truncation_messages_length: f64,
182 pub post_truncation_tokens_in_messages: f64,
183 pub post_truncation_messages_length: f64,
184 pub tokens_removed_during_truncation: f64,
185 pub messages_removed_during_truncation: f64,
186 pub performed_by: String,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191#[serde(rename_all = "camelCase")]
192pub struct UserMessageData {
193 pub content: String,
194 #[serde(skip_serializing_if = "Option::is_none")]
195 pub transformed_content: Option<String>,
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub attachments: Option<Vec<UserMessageAttachmentItem>>,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub source: Option<String>,
200}
201
202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
204pub struct PendingMessagesModifiedData {}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(rename_all = "camelCase")]
209pub struct AssistantTurnStartData {
210 pub turn_id: String,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct AssistantIntentData {
216 pub intent: String,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct AssistantReasoningData {
223 pub reasoning_id: String,
224 pub content: String,
225 #[serde(skip_serializing_if = "Option::is_none")]
226 pub chunk_content: Option<String>,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
231#[serde(rename_all = "camelCase")]
232pub struct AssistantReasoningDeltaData {
233 pub reasoning_id: String,
234 pub delta_content: String,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
239#[serde(rename_all = "camelCase")]
240pub struct AssistantMessageData {
241 pub message_id: String,
242 pub content: String,
243 #[serde(skip_serializing_if = "Option::is_none")]
244 pub chunk_content: Option<String>,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 pub total_response_size_bytes: Option<f64>,
247 #[serde(skip_serializing_if = "Option::is_none")]
248 pub tool_requests: Option<Vec<ToolRequestItem>>,
249 #[serde(skip_serializing_if = "Option::is_none")]
250 pub parent_tool_call_id: Option<String>,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255#[serde(rename_all = "camelCase")]
256pub struct AssistantMessageDeltaData {
257 pub message_id: String,
258 pub delta_content: String,
259 #[serde(skip_serializing_if = "Option::is_none")]
260 pub total_response_size_bytes: Option<f64>,
261 #[serde(skip_serializing_if = "Option::is_none")]
262 pub parent_tool_call_id: Option<String>,
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
267#[serde(rename_all = "camelCase")]
268pub struct AssistantTurnEndData {
269 pub turn_id: String,
270}
271
272#[derive(Debug, Clone, Default, Serialize, Deserialize)]
274#[serde(rename_all = "camelCase")]
275pub struct AssistantUsageData {
276 #[serde(skip_serializing_if = "Option::is_none")]
277 pub model: Option<String>,
278 #[serde(skip_serializing_if = "Option::is_none")]
279 pub input_tokens: Option<f64>,
280 #[serde(skip_serializing_if = "Option::is_none")]
281 pub output_tokens: Option<f64>,
282 #[serde(skip_serializing_if = "Option::is_none")]
283 pub cache_read_tokens: Option<f64>,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub cache_write_tokens: Option<f64>,
286 #[serde(skip_serializing_if = "Option::is_none")]
287 pub cost: Option<f64>,
288 #[serde(skip_serializing_if = "Option::is_none")]
289 pub duration: Option<f64>,
290 #[serde(skip_serializing_if = "Option::is_none")]
291 pub initiator: Option<String>,
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub api_call_id: Option<String>,
294 #[serde(skip_serializing_if = "Option::is_none")]
295 pub provider_call_id: Option<String>,
296 #[serde(skip_serializing_if = "Option::is_none")]
297 pub quota_snapshots: Option<HashMap<String, serde_json::Value>>,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct AbortData {
303 pub reason: String,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308#[serde(rename_all = "camelCase")]
309pub struct ToolUserRequestedData {
310 pub tool_call_id: String,
311 pub tool_name: String,
312 #[serde(skip_serializing_if = "Option::is_none")]
313 pub arguments: Option<serde_json::Value>,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318#[serde(rename_all = "camelCase")]
319pub struct ToolExecutionStartData {
320 pub tool_call_id: String,
321 pub tool_name: String,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 pub arguments: Option<serde_json::Value>,
324 #[serde(skip_serializing_if = "Option::is_none")]
325 pub parent_tool_call_id: Option<String>,
326}
327
328#[derive(Debug, Clone, Serialize, Deserialize)]
330#[serde(rename_all = "camelCase")]
331pub struct ToolExecutionPartialResultData {
332 pub tool_call_id: String,
333 pub partial_output: String,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
338#[serde(rename_all = "camelCase")]
339pub struct ToolExecutionCompleteData {
340 pub tool_call_id: String,
341 pub success: bool,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 pub is_user_requested: Option<bool>,
344 #[serde(skip_serializing_if = "Option::is_none")]
345 pub result: Option<ToolResultContent>,
346 #[serde(skip_serializing_if = "Option::is_none")]
347 pub error: Option<ToolExecutionError>,
348 #[serde(skip_serializing_if = "Option::is_none")]
349 pub tool_telemetry: Option<HashMap<String, serde_json::Value>>,
350 #[serde(skip_serializing_if = "Option::is_none")]
351 pub parent_tool_call_id: Option<String>,
352 #[serde(skip_serializing_if = "Option::is_none")]
353 pub mcp_server_name: Option<String>,
354 #[serde(skip_serializing_if = "Option::is_none")]
355 pub mcp_tool_name: Option<String>,
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize)]
360#[serde(rename_all = "camelCase")]
361pub struct CustomAgentStartedData {
362 pub tool_call_id: String,
363 pub agent_name: String,
364 pub agent_display_name: String,
365 pub agent_description: String,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
370#[serde(rename_all = "camelCase")]
371pub struct CustomAgentCompletedData {
372 pub tool_call_id: String,
373 pub agent_name: String,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
378#[serde(rename_all = "camelCase")]
379pub struct CustomAgentFailedData {
380 pub tool_call_id: String,
381 pub agent_name: String,
382 pub error: String,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize)]
387#[serde(rename_all = "camelCase")]
388pub struct CustomAgentSelectedData {
389 pub agent_name: String,
390 pub agent_display_name: String,
391 pub tools: Vec<String>,
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize)]
396#[serde(rename_all = "camelCase")]
397pub struct HookStartData {
398 pub hook_invocation_id: String,
399 pub hook_type: String,
400 #[serde(skip_serializing_if = "Option::is_none")]
401 pub input: Option<serde_json::Value>,
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize)]
406#[serde(rename_all = "camelCase")]
407pub struct HookEndData {
408 pub hook_invocation_id: String,
409 pub hook_type: String,
410 #[serde(skip_serializing_if = "Option::is_none")]
411 pub output: Option<serde_json::Value>,
412 pub success: bool,
413 #[serde(skip_serializing_if = "Option::is_none")]
414 pub error: Option<HookError>,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
419#[serde(rename_all = "camelCase")]
420pub struct SystemMessageEventData {
421 pub content: String,
422 pub role: SystemMessageRole,
423 #[serde(skip_serializing_if = "Option::is_none")]
424 pub name: Option<String>,
425 #[serde(skip_serializing_if = "Option::is_none")]
426 pub metadata: Option<SystemMessageMetadata>,
427}
428
429#[derive(Debug, Clone, Default, Serialize, Deserialize)]
431pub struct SessionCompactionStartData {}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
435#[serde(rename_all = "camelCase")]
436pub struct CompactionTokensUsed {
437 #[serde(default)]
438 pub input: f64,
439 #[serde(default)]
440 pub output: f64,
441 #[serde(default)]
442 pub cached_input: f64,
443}
444
445#[derive(Debug, Clone, Serialize, Deserialize)]
447#[serde(rename_all = "camelCase")]
448pub struct SessionCompactionCompleteData {
449 pub success: bool,
450 #[serde(skip_serializing_if = "Option::is_none")]
451 pub error: Option<String>,
452 #[serde(skip_serializing_if = "Option::is_none")]
453 pub pre_compaction_tokens: Option<f64>,
454 #[serde(skip_serializing_if = "Option::is_none")]
455 pub post_compaction_tokens: Option<f64>,
456 #[serde(skip_serializing_if = "Option::is_none")]
457 pub pre_compaction_messages_length: Option<f64>,
458 #[serde(skip_serializing_if = "Option::is_none")]
459 pub post_compaction_messages_length: Option<f64>,
460 #[serde(skip_serializing_if = "Option::is_none")]
461 pub compaction_tokens_used: Option<CompactionTokensUsed>,
462 #[serde(skip_serializing_if = "Option::is_none")]
463 pub messages_removed: Option<f64>,
464 #[serde(skip_serializing_if = "Option::is_none")]
465 pub tokens_removed: Option<f64>,
466 #[serde(skip_serializing_if = "Option::is_none")]
467 pub summary_content: Option<String>,
468 #[serde(skip_serializing_if = "Option::is_none")]
469 pub checkpoint_number: Option<f64>,
470 #[serde(skip_serializing_if = "Option::is_none")]
471 pub checkpoint_path: Option<String>,
472}
473
474#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
476#[serde(rename_all = "lowercase")]
477pub enum ShutdownType {
478 Routine,
479 Error,
480}
481
482#[derive(Debug, Clone, Default, Serialize, Deserialize)]
484#[serde(rename_all = "camelCase")]
485pub struct ShutdownCodeChanges {
486 #[serde(default)]
487 pub lines_added: f64,
488 #[serde(default)]
489 pub lines_removed: f64,
490 #[serde(default)]
491 pub files_modified: Vec<String>,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize)]
496#[serde(rename_all = "camelCase")]
497pub struct SessionShutdownData {
498 pub shutdown_type: ShutdownType,
499 #[serde(skip_serializing_if = "Option::is_none")]
500 pub error_reason: Option<String>,
501 #[serde(default)]
502 pub total_premium_requests: f64,
503 #[serde(default)]
504 pub total_api_duration_ms: f64,
505 #[serde(default)]
506 pub session_start_time: f64,
507 #[serde(default)]
508 pub code_changes: ShutdownCodeChanges,
509 #[serde(default)]
510 pub model_metrics: HashMap<String, serde_json::Value>,
511 #[serde(skip_serializing_if = "Option::is_none")]
512 pub current_model: Option<String>,
513}
514
515#[derive(Debug, Clone, Serialize, Deserialize)]
517#[serde(rename_all = "camelCase")]
518pub struct SessionSnapshotRewindData {
519 pub up_to_event_id: String,
520 #[serde(default)]
521 pub events_removed: f64,
522}
523
524#[derive(Debug, Clone, Serialize, Deserialize)]
526#[serde(rename_all = "camelCase")]
527pub struct SessionUsageInfoData {
528 #[serde(default)]
529 pub token_limit: f64,
530 #[serde(default)]
531 pub current_tokens: f64,
532 #[serde(default)]
533 pub messages_length: f64,
534}
535
536#[derive(Debug, Clone, Serialize, Deserialize)]
538#[serde(rename_all = "camelCase")]
539pub struct ToolExecutionProgressData {
540 pub tool_call_id: String,
541 pub progress_message: String,
542}
543
544#[derive(Debug, Clone, Serialize, Deserialize)]
546#[serde(rename_all = "camelCase")]
547pub struct SkillInvokedData {
548 pub name: String,
549 pub path: String,
550 pub content: String,
551 #[serde(skip_serializing_if = "Option::is_none")]
552 pub allowed_tools: Option<Vec<String>>,
553}
554
555#[derive(Debug, Clone, Serialize, Deserialize)]
561#[serde(untagged)]
562pub enum SessionEventData {
563 SessionStart(SessionStartData),
564 SessionResume(SessionResumeData),
565 SessionError(SessionErrorData),
566 SessionIdle(SessionIdleData),
567 SessionInfo(SessionInfoData),
568 SessionModelChange(SessionModelChangeData),
569 SessionHandoff(SessionHandoffData),
570 SessionTruncation(SessionTruncationData),
571 UserMessage(UserMessageData),
572 PendingMessagesModified(PendingMessagesModifiedData),
573 AssistantTurnStart(AssistantTurnStartData),
574 AssistantIntent(AssistantIntentData),
575 AssistantReasoning(AssistantReasoningData),
576 AssistantReasoningDelta(AssistantReasoningDeltaData),
577 AssistantMessage(AssistantMessageData),
578 AssistantMessageDelta(AssistantMessageDeltaData),
579 AssistantTurnEnd(AssistantTurnEndData),
580 AssistantUsage(AssistantUsageData),
581 Abort(AbortData),
582 ToolUserRequested(ToolUserRequestedData),
583 ToolExecutionStart(ToolExecutionStartData),
584 ToolExecutionPartialResult(ToolExecutionPartialResultData),
585 ToolExecutionComplete(ToolExecutionCompleteData),
586 ToolExecutionProgress(ToolExecutionProgressData),
587 CustomAgentStarted(CustomAgentStartedData),
588 CustomAgentCompleted(CustomAgentCompletedData),
589 CustomAgentFailed(CustomAgentFailedData),
590 CustomAgentSelected(CustomAgentSelectedData),
591 HookStart(HookStartData),
592 HookEnd(HookEndData),
593 SystemMessage(SystemMessageEventData),
594 SessionCompactionStart(SessionCompactionStartData),
595 SessionCompactionComplete(SessionCompactionCompleteData),
596 SessionShutdown(SessionShutdownData),
597 SessionSnapshotRewind(SessionSnapshotRewindData),
598 SessionUsageInfo(SessionUsageInfoData),
599 SkillInvoked(SkillInvokedData),
600 Unknown(serde_json::Value),
602}
603
604#[derive(Debug, Clone, Serialize, Deserialize)]
609#[serde(rename_all = "camelCase")]
610pub struct RawSessionEvent {
611 pub id: String,
612 pub timestamp: String,
613 #[serde(rename = "type")]
614 pub event_type: String,
615 #[serde(skip_serializing_if = "Option::is_none")]
616 pub parent_id: Option<String>,
617 #[serde(skip_serializing_if = "Option::is_none")]
618 pub ephemeral: Option<bool>,
619 pub data: serde_json::Value,
620}
621
622#[derive(Debug, Clone)]
624pub struct SessionEvent {
625 pub id: String,
627 pub timestamp: String,
629 pub event_type: String,
631 pub parent_id: Option<String>,
633 pub ephemeral: Option<bool>,
635 pub data: SessionEventData,
637}
638
639impl SessionEvent {
640 pub fn from_json(json: &serde_json::Value) -> Result<Self, serde_json::Error> {
642 let raw: RawSessionEvent = serde_json::from_value(json.clone())?;
643 Ok(Self::from_raw(raw))
644 }
645
646 pub fn from_raw(raw: RawSessionEvent) -> Self {
648 let data = parse_event_data(&raw.event_type, raw.data);
649 Self {
650 id: raw.id,
651 timestamp: raw.timestamp,
652 event_type: raw.event_type,
653 parent_id: raw.parent_id,
654 ephemeral: raw.ephemeral,
655 data,
656 }
657 }
658
659 pub fn is_assistant_message(&self) -> bool {
665 matches!(self.data, SessionEventData::AssistantMessage(_))
666 }
667
668 pub fn is_assistant_message_delta(&self) -> bool {
670 matches!(self.data, SessionEventData::AssistantMessageDelta(_))
671 }
672
673 pub fn is_session_idle(&self) -> bool {
675 matches!(self.data, SessionEventData::SessionIdle(_))
676 }
677
678 pub fn is_session_error(&self) -> bool {
680 matches!(self.data, SessionEventData::SessionError(_))
681 }
682
683 pub fn is_terminal(&self) -> bool {
685 matches!(
686 self.data,
687 SessionEventData::SessionIdle(_) | SessionEventData::SessionError(_)
688 )
689 }
690
691 pub fn as_assistant_message(&self) -> Option<&AssistantMessageData> {
697 match &self.data {
698 SessionEventData::AssistantMessage(data) => Some(data),
699 _ => None,
700 }
701 }
702
703 pub fn as_assistant_message_delta(&self) -> Option<&AssistantMessageDeltaData> {
705 match &self.data {
706 SessionEventData::AssistantMessageDelta(data) => Some(data),
707 _ => None,
708 }
709 }
710
711 pub fn as_session_error(&self) -> Option<&SessionErrorData> {
713 match &self.data {
714 SessionEventData::SessionError(data) => Some(data),
715 _ => None,
716 }
717 }
718
719 pub fn as_tool_execution_complete(&self) -> Option<&ToolExecutionCompleteData> {
721 match &self.data {
722 SessionEventData::ToolExecutionComplete(data) => Some(data),
723 _ => None,
724 }
725 }
726
727 pub fn content(&self) -> Option<&str> {
729 match &self.data {
730 SessionEventData::AssistantMessage(data) => Some(&data.content),
731 SessionEventData::AssistantMessageDelta(data) => Some(&data.delta_content),
732 _ => None,
733 }
734 }
735}
736
737fn parse_event_data(event_type: &str, data: serde_json::Value) -> SessionEventData {
739 match event_type {
740 "session.start" => serde_json::from_value(data)
741 .map(SessionEventData::SessionStart)
742 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
743 "session.resume" => serde_json::from_value(data)
744 .map(SessionEventData::SessionResume)
745 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
746 "session.error" => serde_json::from_value(data)
747 .map(SessionEventData::SessionError)
748 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
749 "session.idle" => SessionEventData::SessionIdle(SessionIdleData {}),
750 "session.info" => serde_json::from_value(data)
751 .map(SessionEventData::SessionInfo)
752 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
753 "session.model_change" => serde_json::from_value(data)
754 .map(SessionEventData::SessionModelChange)
755 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
756 "session.handoff" => serde_json::from_value(data)
757 .map(SessionEventData::SessionHandoff)
758 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
759 "session.truncation" => serde_json::from_value(data)
760 .map(SessionEventData::SessionTruncation)
761 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
762 "user.message" => serde_json::from_value(data)
763 .map(SessionEventData::UserMessage)
764 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
765 "pending_messages.modified" => {
766 SessionEventData::PendingMessagesModified(PendingMessagesModifiedData {})
767 }
768 "assistant.turn_start" => serde_json::from_value(data)
769 .map(SessionEventData::AssistantTurnStart)
770 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
771 "assistant.intent" => serde_json::from_value(data)
772 .map(SessionEventData::AssistantIntent)
773 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
774 "assistant.reasoning" => serde_json::from_value(data)
775 .map(SessionEventData::AssistantReasoning)
776 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
777 "assistant.reasoning_delta" => serde_json::from_value(data)
778 .map(SessionEventData::AssistantReasoningDelta)
779 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
780 "assistant.message" => serde_json::from_value(data)
781 .map(SessionEventData::AssistantMessage)
782 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
783 "assistant.message_delta" => serde_json::from_value(data)
784 .map(SessionEventData::AssistantMessageDelta)
785 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
786 "assistant.turn_end" => serde_json::from_value(data)
787 .map(SessionEventData::AssistantTurnEnd)
788 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
789 "assistant.usage" => serde_json::from_value(data)
790 .map(SessionEventData::AssistantUsage)
791 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
792 "abort" => serde_json::from_value(data)
793 .map(SessionEventData::Abort)
794 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
795 "tool.user_requested" => serde_json::from_value(data)
796 .map(SessionEventData::ToolUserRequested)
797 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
798 "tool.execution_start" => serde_json::from_value(data)
799 .map(SessionEventData::ToolExecutionStart)
800 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
801 "tool.execution_partial_result" => serde_json::from_value(data)
802 .map(SessionEventData::ToolExecutionPartialResult)
803 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
804 "tool.execution_complete" => serde_json::from_value(data)
805 .map(SessionEventData::ToolExecutionComplete)
806 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
807 "tool.execution_progress" => serde_json::from_value(data)
808 .map(SessionEventData::ToolExecutionProgress)
809 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
810 "subagent.started" | "custom_agent.started" => serde_json::from_value(data)
812 .map(SessionEventData::CustomAgentStarted)
813 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
814 "subagent.completed" | "custom_agent.completed" => serde_json::from_value(data)
815 .map(SessionEventData::CustomAgentCompleted)
816 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
817 "subagent.failed" | "custom_agent.failed" => serde_json::from_value(data)
818 .map(SessionEventData::CustomAgentFailed)
819 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
820 "subagent.selected" | "custom_agent.selected" => serde_json::from_value(data)
821 .map(SessionEventData::CustomAgentSelected)
822 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
823 "hook.start" => serde_json::from_value(data)
824 .map(SessionEventData::HookStart)
825 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
826 "hook.end" => serde_json::from_value(data)
827 .map(SessionEventData::HookEnd)
828 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
829 "system.message" => serde_json::from_value(data)
830 .map(SessionEventData::SystemMessage)
831 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
832 "session.compaction_start" => {
833 SessionEventData::SessionCompactionStart(SessionCompactionStartData {})
834 }
835 "session.compaction_complete" => serde_json::from_value(data)
836 .map(SessionEventData::SessionCompactionComplete)
837 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
838 "session.shutdown" => serde_json::from_value(data)
839 .map(SessionEventData::SessionShutdown)
840 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
841 "session.snapshot_rewind" => serde_json::from_value(data)
842 .map(SessionEventData::SessionSnapshotRewind)
843 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
844 "session.usage_info" => serde_json::from_value(data)
845 .map(SessionEventData::SessionUsageInfo)
846 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
847 "skill.invoked" => serde_json::from_value(data)
848 .map(SessionEventData::SkillInvoked)
849 .unwrap_or_else(|_| SessionEventData::Unknown(serde_json::Value::Null)),
850 _ => SessionEventData::Unknown(data),
852 }
853}
854
855#[cfg(test)]
856mod tests {
857 use super::*;
858 use serde_json::json;
859
860 #[test]
861 fn test_parse_assistant_message() {
862 let json = json!({
863 "id": "evt_123",
864 "timestamp": "2024-01-15T10:30:00Z",
865 "type": "assistant.message",
866 "data": {
867 "messageId": "msg_456",
868 "content": "Hello, world!"
869 }
870 });
871
872 let event = SessionEvent::from_json(&json).unwrap();
873 assert_eq!(event.id, "evt_123");
874 assert_eq!(event.event_type, "assistant.message");
875 assert!(event.is_assistant_message());
876
877 let msg = event.as_assistant_message().unwrap();
878 assert_eq!(msg.message_id, "msg_456");
879 assert_eq!(msg.content, "Hello, world!");
880 }
881
882 #[test]
883 fn test_parse_assistant_message_delta() {
884 let json = json!({
885 "id": "evt_124",
886 "timestamp": "2024-01-15T10:30:01Z",
887 "type": "assistant.message_delta",
888 "data": {
889 "messageId": "msg_456",
890 "deltaContent": "Hello"
891 }
892 });
893
894 let event = SessionEvent::from_json(&json).unwrap();
895 assert!(event.is_assistant_message_delta());
896 assert_eq!(event.content(), Some("Hello"));
897 }
898
899 #[test]
900 fn test_parse_session_idle() {
901 let json = json!({
902 "id": "evt_125",
903 "timestamp": "2024-01-15T10:30:02Z",
904 "type": "session.idle",
905 "data": {}
906 });
907
908 let event = SessionEvent::from_json(&json).unwrap();
909 assert!(event.is_session_idle());
910 assert!(event.is_terminal());
911 }
912
913 #[test]
914 fn test_parse_session_error() {
915 let json = json!({
916 "id": "evt_126",
917 "timestamp": "2024-01-15T10:30:03Z",
918 "type": "session.error",
919 "data": {
920 "errorType": "api_error",
921 "message": "Rate limit exceeded"
922 }
923 });
924
925 let event = SessionEvent::from_json(&json).unwrap();
926 assert!(event.is_session_error());
927 assert!(event.is_terminal());
928
929 let err = event.as_session_error().unwrap();
930 assert_eq!(err.error_type, "api_error");
931 assert_eq!(err.message, "Rate limit exceeded");
932 }
933
934 #[test]
935 fn test_parse_tool_execution_complete() {
936 let json = json!({
937 "id": "evt_127",
938 "timestamp": "2024-01-15T10:30:04Z",
939 "type": "tool.execution_complete",
940 "data": {
941 "toolCallId": "call_789",
942 "success": true,
943 "result": {
944 "content": "Tool output"
945 }
946 }
947 });
948
949 let event = SessionEvent::from_json(&json).unwrap();
950 let tool = event.as_tool_execution_complete().unwrap();
951 assert_eq!(tool.tool_call_id, "call_789");
952 assert!(tool.success);
953 assert_eq!(tool.result.as_ref().unwrap().content, "Tool output");
954 }
955
956 #[test]
957 fn test_parse_unknown_event() {
958 let json = json!({
959 "id": "evt_128",
960 "timestamp": "2024-01-15T10:30:05Z",
961 "type": "future.unknown_event",
962 "data": {
963 "someField": "someValue"
964 }
965 });
966
967 let event = SessionEvent::from_json(&json).unwrap();
968 assert_eq!(event.event_type, "future.unknown_event");
969 assert!(matches!(event.data, SessionEventData::Unknown(_)));
970 }
971
972 #[test]
973 fn test_parse_session_start() {
974 let json = json!({
975 "id": "evt_001",
976 "timestamp": "2024-01-15T10:30:00Z",
977 "type": "session.start",
978 "data": {
979 "sessionId": "sess_123",
980 "version": 1.0,
981 "producer": "copilot-cli",
982 "copilotVersion": "1.0.0",
983 "startTime": "2024-01-15T10:30:00Z"
984 }
985 });
986
987 let event = SessionEvent::from_json(&json).unwrap();
988 if let SessionEventData::SessionStart(data) = &event.data {
989 assert_eq!(data.session_id, "sess_123");
990 assert_eq!(data.producer, "copilot-cli");
991 } else {
992 panic!("Expected SessionStart");
993 }
994 }
995
996 #[test]
997 fn test_event_with_parent_id() {
998 let json = json!({
999 "id": "evt_129",
1000 "timestamp": "2024-01-15T10:30:06Z",
1001 "type": "assistant.message",
1002 "parentId": "evt_128",
1003 "ephemeral": true,
1004 "data": {
1005 "messageId": "msg_789",
1006 "content": "Nested message"
1007 }
1008 });
1009
1010 let event = SessionEvent::from_json(&json).unwrap();
1011 assert_eq!(event.parent_id, Some("evt_128".to_string()));
1012 assert_eq!(event.ephemeral, Some(true));
1013 }
1014
1015 #[test]
1016 fn test_parse_subagent_started() {
1017 let json = json!({
1018 "id": "evt_200",
1019 "timestamp": "2024-01-15T10:30:00Z",
1020 "type": "subagent.started",
1021 "data": {
1022 "toolCallId": "call_1",
1023 "agentName": "test-agent",
1024 "agentDisplayName": "Test Agent",
1025 "agentDescription": "A test agent"
1026 }
1027 });
1028 let event = SessionEvent::from_json(&json).unwrap();
1029 assert!(matches!(
1030 event.data,
1031 SessionEventData::CustomAgentStarted(_)
1032 ));
1033 if let SessionEventData::CustomAgentStarted(data) = &event.data {
1034 assert_eq!(data.agent_name, "test-agent");
1035 }
1036 }
1037
1038 #[test]
1039 fn test_parse_subagent_completed_legacy_alias() {
1040 let json = json!({
1042 "id": "evt_201",
1043 "timestamp": "2024-01-15T10:30:00Z",
1044 "type": "custom_agent.completed",
1045 "data": {
1046 "toolCallId": "call_1",
1047 "agentName": "test-agent"
1048 }
1049 });
1050 let event = SessionEvent::from_json(&json).unwrap();
1051 assert!(matches!(
1052 event.data,
1053 SessionEventData::CustomAgentCompleted(_)
1054 ));
1055 }
1056
1057 #[test]
1058 fn test_parse_subagent_all_wire_names() {
1059 for wire_name in &["subagent.failed", "custom_agent.failed"] {
1060 let json = json!({
1061 "id": "evt_202",
1062 "timestamp": "2024-01-15T10:30:00Z",
1063 "type": wire_name,
1064 "data": {
1065 "toolCallId": "call_1",
1066 "agentName": "agent",
1067 "error": "boom"
1068 }
1069 });
1070 let event = SessionEvent::from_json(&json).unwrap();
1071 assert!(
1072 matches!(event.data, SessionEventData::CustomAgentFailed(_)),
1073 "Failed to parse {wire_name}"
1074 );
1075 }
1076 }
1077
1078 #[test]
1079 fn test_parse_session_compaction_start() {
1080 let json = json!({
1081 "id": "evt_300",
1082 "timestamp": "2024-01-15T10:30:00Z",
1083 "type": "session.compaction_start",
1084 "data": {}
1085 });
1086 let event = SessionEvent::from_json(&json).unwrap();
1087 assert!(matches!(
1088 event.data,
1089 SessionEventData::SessionCompactionStart(_)
1090 ));
1091 }
1092
1093 #[test]
1094 fn test_parse_session_compaction_complete() {
1095 let json = json!({
1096 "id": "evt_301",
1097 "timestamp": "2024-01-15T10:30:00Z",
1098 "type": "session.compaction_complete",
1099 "data": {
1100 "success": true,
1101 "preCompactionTokens": 50000.0,
1102 "postCompactionTokens": 10000.0,
1103 "compactionTokensUsed": {
1104 "input": 100.0,
1105 "output": 200.0,
1106 "cachedInput": 50.0
1107 },
1108 "summaryContent": "Session was compacted"
1109 }
1110 });
1111 let event = SessionEvent::from_json(&json).unwrap();
1112 if let SessionEventData::SessionCompactionComplete(data) = &event.data {
1113 assert!(data.success);
1114 assert_eq!(data.pre_compaction_tokens, Some(50000.0));
1115 assert_eq!(data.compaction_tokens_used.as_ref().unwrap().input, 100.0);
1116 } else {
1117 panic!("Expected SessionCompactionComplete");
1118 }
1119 }
1120
1121 #[test]
1122 fn test_parse_session_shutdown() {
1123 let json = json!({
1124 "id": "evt_302",
1125 "timestamp": "2024-01-15T10:30:00Z",
1126 "type": "session.shutdown",
1127 "data": {
1128 "shutdownType": "routine",
1129 "totalPremiumRequests": 5.0,
1130 "totalApiDurationMs": 1200.0,
1131 "sessionStartTime": 1700000000.0,
1132 "codeChanges": {
1133 "linesAdded": 10.0,
1134 "linesRemoved": 3.0,
1135 "filesModified": ["src/main.rs"]
1136 },
1137 "modelMetrics": {},
1138 "currentModel": "gpt-4"
1139 }
1140 });
1141 let event = SessionEvent::from_json(&json).unwrap();
1142 if let SessionEventData::SessionShutdown(data) = &event.data {
1143 assert_eq!(data.shutdown_type, ShutdownType::Routine);
1144 assert_eq!(data.current_model, Some("gpt-4".to_string()));
1145 assert_eq!(data.code_changes.lines_added, 10.0);
1146 } else {
1147 panic!("Expected SessionShutdown");
1148 }
1149 }
1150
1151 #[test]
1152 fn test_parse_session_snapshot_rewind() {
1153 let json = json!({
1154 "id": "evt_303",
1155 "timestamp": "2024-01-15T10:30:00Z",
1156 "type": "session.snapshot_rewind",
1157 "data": {
1158 "upToEventId": "evt_100",
1159 "eventsRemoved": 5.0
1160 }
1161 });
1162 let event = SessionEvent::from_json(&json).unwrap();
1163 if let SessionEventData::SessionSnapshotRewind(data) = &event.data {
1164 assert_eq!(data.up_to_event_id, "evt_100");
1165 assert_eq!(data.events_removed, 5.0);
1166 } else {
1167 panic!("Expected SessionSnapshotRewind");
1168 }
1169 }
1170
1171 #[test]
1172 fn test_parse_session_usage_info() {
1173 let json = json!({
1174 "id": "evt_304",
1175 "timestamp": "2024-01-15T10:30:00Z",
1176 "type": "session.usage_info",
1177 "data": {
1178 "tokenLimit": 100000.0,
1179 "currentTokens": 50000.0,
1180 "messagesLength": 42.0
1181 }
1182 });
1183 let event = SessionEvent::from_json(&json).unwrap();
1184 if let SessionEventData::SessionUsageInfo(data) = &event.data {
1185 assert_eq!(data.token_limit, 100000.0);
1186 assert_eq!(data.current_tokens, 50000.0);
1187 } else {
1188 panic!("Expected SessionUsageInfo");
1189 }
1190 }
1191
1192 #[test]
1193 fn test_parse_tool_execution_progress() {
1194 let json = json!({
1195 "id": "evt_305",
1196 "timestamp": "2024-01-15T10:30:00Z",
1197 "type": "tool.execution_progress",
1198 "data": {
1199 "toolCallId": "call_100",
1200 "progressMessage": "Processing file 3 of 10..."
1201 }
1202 });
1203 let event = SessionEvent::from_json(&json).unwrap();
1204 if let SessionEventData::ToolExecutionProgress(data) = &event.data {
1205 assert_eq!(data.tool_call_id, "call_100");
1206 assert_eq!(data.progress_message, "Processing file 3 of 10...");
1207 } else {
1208 panic!("Expected ToolExecutionProgress");
1209 }
1210 }
1211
1212 #[test]
1213 fn test_parse_skill_invoked() {
1214 let json = json!({
1215 "id": "evt_306",
1216 "timestamp": "2024-01-15T10:30:00Z",
1217 "type": "skill.invoked",
1218 "data": {
1219 "name": "code-review",
1220 "path": "/skills/code-review",
1221 "content": "Review this code",
1222 "allowedTools": ["read_file", "search"]
1223 }
1224 });
1225 let event = SessionEvent::from_json(&json).unwrap();
1226 if let SessionEventData::SkillInvoked(data) = &event.data {
1227 assert_eq!(data.name, "code-review");
1228 assert_eq!(data.allowed_tools.as_ref().unwrap().len(), 2);
1229 } else {
1230 panic!("Expected SkillInvoked");
1231 }
1232 }
1233
1234 #[test]
1235 fn test_session_error_with_code_and_provider_call_id() {
1236 let json = json!({
1237 "id": "evt_err",
1238 "timestamp": "2024-01-15T10:30:00Z",
1239 "type": "session.error",
1240 "data": {
1241 "errorType": "provider_error",
1242 "message": "Rate limited",
1243 "code": 429.0,
1244 "providerCallId": "call-abc-123"
1245 }
1246 });
1247 let event = SessionEvent::from_json(&json).unwrap();
1248 if let SessionEventData::SessionError(data) = &event.data {
1249 assert_eq!(data.error_type, "provider_error");
1250 assert_eq!(data.code, Some(429.0));
1251 assert_eq!(data.provider_call_id.as_deref(), Some("call-abc-123"));
1252 } else {
1253 panic!("Expected SessionError");
1254 }
1255 }
1256
1257 #[test]
1258 fn test_tool_execution_complete_with_mcp_fields() {
1259 let json = json!({
1260 "id": "evt_mcp",
1261 "timestamp": "2024-01-15T10:30:00Z",
1262 "type": "tool.execution_complete",
1263 "data": {
1264 "toolCallId": "call-1",
1265 "success": true,
1266 "mcpServerName": "my-server",
1267 "mcpToolName": "read_file"
1268 }
1269 });
1270 let event = SessionEvent::from_json(&json).unwrap();
1271 if let SessionEventData::ToolExecutionComplete(data) = &event.data {
1272 assert_eq!(data.mcp_server_name.as_deref(), Some("my-server"));
1273 assert_eq!(data.mcp_tool_name.as_deref(), Some("read_file"));
1274 } else {
1275 panic!("Expected ToolExecutionComplete");
1276 }
1277 }
1278
1279 #[test]
1280 fn test_session_start_data_optional_fields() {
1281 let json = json!({
1283 "id": "evt_start",
1284 "timestamp": "2024-01-15T10:30:00Z",
1285 "type": "session.start",
1286 "data": {}
1287 });
1288 let event = SessionEvent::from_json(&json).unwrap();
1289 if let SessionEventData::SessionStart(data) = &event.data {
1290 assert_eq!(data.session_id, "");
1291 assert_eq!(data.version, 0.0);
1292 assert_eq!(data.producer, "");
1293 } else {
1294 panic!("Expected SessionStart");
1295 }
1296 }
1297
1298 #[test]
1299 fn test_unknown_event_type_handled_gracefully() {
1300 let json = json!({
1301 "id": "evt_unknown",
1302 "timestamp": "2025-01-01T00:00:00Z",
1303 "type": "some.future.event.type",
1304 "data": {"someField": "someValue"}
1305 });
1306 let raw: RawSessionEvent = serde_json::from_value(json.clone()).unwrap();
1308 assert_eq!(raw.event_type, "some.future.event.type");
1309
1310 let event = SessionEvent::from_json(&json).unwrap();
1312 assert_eq!(event.event_type, "some.future.event.type");
1313 assert!(matches!(event.data, SessionEventData::Unknown(_)));
1314 }
1315
1316 #[test]
1317 fn test_session_shutdown_event_parsed() {
1318 let json = json!({
1319 "id": "evt_shutdown",
1320 "timestamp": "2025-01-01T00:00:00Z",
1321 "type": "session.shutdown",
1322 "data": {
1323 "shutdownType": "routine",
1324 "reason": "user requested"
1325 }
1326 });
1327 let raw: RawSessionEvent = serde_json::from_value(json.clone()).unwrap();
1328 assert_eq!(raw.event_type, "session.shutdown");
1329
1330 let event = SessionEvent::from_json(&json).unwrap();
1331 assert_eq!(event.event_type, "session.shutdown");
1332 }
1333
1334 #[test]
1335 fn test_session_usage_info_recognized() {
1336 let json = json!({
1337 "id": "evt_usage",
1338 "timestamp": "2025-01-01T00:00:00Z",
1339 "type": "session.usage_info",
1340 "data": {}
1341 });
1342 let raw: RawSessionEvent = serde_json::from_value(json.clone()).unwrap();
1343 assert_eq!(raw.event_type, "session.usage_info");
1344
1345 let event = SessionEvent::from_json(&json).unwrap();
1346 assert_eq!(event.event_type, "session.usage_info");
1347 }
1348}