1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum HookEventType {
11 PreToolUse,
13 PostToolUse,
15 PermissionRequest,
17 PreCompact,
19 PostCompact,
21 GenerateStart,
23 GenerateEnd,
25 SessionStart,
27 SessionEnd,
29 SkillLoad,
31 SkillUnload,
33 PrePrompt,
35 PostResponse,
37 OnError,
39 PreContextPerception,
42 PostContextPerception,
44
45 OnSuccess,
47
48 PreMemoryRecall,
50 PostMemoryRecall,
52
53 PrePlanning,
55 PostPlanning,
57
58 PreReasoning,
60 PostReasoning,
62
63 OnRateLimit,
65
66 OnConfirmation,
68
69 IntentDetection,
71}
72
73impl std::fmt::Display for HookEventType {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 HookEventType::PreToolUse => write!(f, "pre_tool_use"),
77 HookEventType::PostToolUse => write!(f, "post_tool_use"),
78 HookEventType::PermissionRequest => write!(f, "permission_request"),
79 HookEventType::PreCompact => write!(f, "pre_compact"),
80 HookEventType::PostCompact => write!(f, "post_compact"),
81 HookEventType::GenerateStart => write!(f, "generate_start"),
82 HookEventType::GenerateEnd => write!(f, "generate_end"),
83 HookEventType::SessionStart => write!(f, "session_start"),
84 HookEventType::SessionEnd => write!(f, "session_end"),
85 HookEventType::SkillLoad => write!(f, "skill_load"),
86 HookEventType::SkillUnload => write!(f, "skill_unload"),
87 HookEventType::PrePrompt => write!(f, "pre_prompt"),
88 HookEventType::PostResponse => write!(f, "post_response"),
89 HookEventType::OnError => write!(f, "on_error"),
90 HookEventType::PreContextPerception => write!(f, "pre_context_perception"),
92 HookEventType::PostContextPerception => write!(f, "post_context_perception"),
93 HookEventType::OnSuccess => write!(f, "on_success"),
94 HookEventType::PreMemoryRecall => write!(f, "pre_memory_recall"),
95 HookEventType::PostMemoryRecall => write!(f, "post_memory_recall"),
96 HookEventType::PrePlanning => write!(f, "pre_planning"),
97 HookEventType::PostPlanning => write!(f, "post_planning"),
98 HookEventType::PreReasoning => write!(f, "pre_reasoning"),
99 HookEventType::PostReasoning => write!(f, "post_reasoning"),
100 HookEventType::OnRateLimit => write!(f, "on_rate_limit"),
101 HookEventType::OnConfirmation => write!(f, "on_confirmation"),
102 HookEventType::IntentDetection => write!(f, "intent_detection"),
103 }
104 }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct ToolResultData {
110 pub success: bool,
112 pub output: String,
114 pub exit_code: Option<i32>,
116 pub duration_ms: u64,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct PreToolUseEvent {
123 pub session_id: String,
125 pub tool: String,
127 pub args: serde_json::Value,
129 pub working_directory: String,
131 pub recent_tools: Vec<String>,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct PostToolUseEvent {
138 pub session_id: String,
140 pub tool: String,
142 pub args: serde_json::Value,
144 pub result: ToolResultData,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct PermissionRequestEvent {
151 pub session_id: String,
152 pub tool_id: String,
153 pub tool: String,
154 pub args: serde_json::Value,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct PreCompactEvent {
160 pub session_id: String,
161 pub message_count: usize,
162 pub used_tokens: usize,
163 pub max_tokens: usize,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct PostCompactEvent {
169 pub session_id: String,
170 pub message_count_before: usize,
171 pub message_count_after: usize,
172 pub summary_generated: bool,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct GenerateStartEvent {
178 pub session_id: String,
180 pub prompt: String,
182 pub system_prompt: Option<String>,
184 pub model_provider: String,
186 pub model_name: String,
188 pub available_tools: Vec<String>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct GenerateEndEvent {
195 pub session_id: String,
197 pub prompt: String,
199 pub response_text: String,
201 pub tool_calls: Vec<ToolCallInfo>,
203 pub usage: TokenUsageInfo,
205 pub duration_ms: u64,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct ToolCallInfo {
212 pub name: String,
214 pub args: serde_json::Value,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct TokenUsageInfo {
221 pub prompt_tokens: i32,
223 pub completion_tokens: i32,
225 pub total_tokens: i32,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct SessionStartEvent {
232 pub session_id: String,
234 pub system_prompt: Option<String>,
236 pub model_provider: String,
238 pub model_name: String,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct SessionEndEvent {
244 pub session_id: String,
246 pub total_tokens: i32,
248 pub total_tool_calls: i32,
250 pub duration_ms: u64,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct SkillLoadEvent {
257 pub skill_name: String,
259 pub tool_names: Vec<String>,
261 pub version: Option<String>,
263 pub description: Option<String>,
265 pub loaded_at: i64,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct SkillUnloadEvent {
272 pub skill_name: String,
274 pub tool_names: Vec<String>,
276 pub duration_ms: u64,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct PrePromptEvent {
283 pub session_id: String,
285 pub prompt: String,
287 pub system_prompt: Option<String>,
289 pub message_count: usize,
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct PostResponseEvent {
296 pub session_id: String,
298 pub response_text: String,
300 pub tool_calls_count: usize,
302 pub usage: TokenUsageInfo,
304 pub duration_ms: u64,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310#[serde(rename_all = "snake_case")]
311pub enum ErrorType {
312 ToolFailure,
314 LlmFailure,
316 PermissionDenied,
318 Timeout,
320 Other,
322}
323
324impl std::fmt::Display for ErrorType {
325 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
326 match self {
327 ErrorType::ToolFailure => write!(f, "tool_failure"),
328 ErrorType::LlmFailure => write!(f, "llm_failure"),
329 ErrorType::PermissionDenied => write!(f, "permission_denied"),
330 ErrorType::Timeout => write!(f, "timeout"),
331 ErrorType::Other => write!(f, "other"),
332 }
333 }
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct OnErrorEvent {
339 pub session_id: String,
341 pub error_type: ErrorType,
343 pub error_message: String,
345 pub context: serde_json::Value,
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct PreContextPerceptionEvent {
356 pub session_id: String,
357 pub intent: String,
358 pub target_type: String,
359 pub target_name: String,
360 pub domain: String,
361 pub query: Option<String>,
362 pub working_directory: String,
363 pub urgency: String,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct PostContextPerceptionEvent {
369 pub session_id: String,
370 pub intent: String,
371 pub target_type: String,
372 pub success: bool,
373 pub facts_retrieved: usize,
374 pub files_retrieved: usize,
375 #[serde(skip_serializing_if = "Option::is_none")]
376 pub error: Option<String>,
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct OnSuccessEvent {
382 pub session_id: String,
384 pub action_type: String,
386 pub action_summary: String,
388 pub duration_ms: u64,
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct PreMemoryRecallEvent {
395 pub session_id: String,
396 pub query: String,
398 pub memory_type: String,
400 pub max_results: usize,
402 pub working_directory: String,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct PostMemoryRecallEvent {
409 pub session_id: String,
410 pub query: String,
411 pub memory_type: String,
412 pub facts_retrieved: usize,
413 pub success: bool,
414 #[serde(skip_serializing_if = "Option::is_none")]
415 pub error: Option<String>,
416}
417
418#[derive(Debug, Clone, Serialize, Deserialize)]
420#[serde(rename_all = "snake_case")]
421pub enum PlanningStrategy {
422 None,
423 StepByStep,
424 TreeOfThoughts,
425 GraphPlanning,
426 Custom(String),
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct PrePlanningEvent {
432 pub session_id: String,
433 pub task_description: String,
435 pub available_strategies: Vec<PlanningStrategy>,
437 #[serde(skip_serializing_if = "Option::is_none")]
439 pub constraints: Option<serde_json::Value>,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct PostPlanningEvent {
445 pub session_id: String,
446 pub task_description: String,
447 pub strategy_used: PlanningStrategy,
448 pub subtasks: Vec<String>,
450 pub success: bool,
451 #[serde(skip_serializing_if = "Option::is_none")]
452 pub error: Option<String>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
457#[serde(rename_all = "snake_case")]
458pub enum ReasoningType {
459 ChainOfThought,
460 TreeOfThoughts,
461 ReAct,
462 Reflexion,
463 Other(String),
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct PreReasoningEvent {
469 pub session_id: String,
470 pub reasoning_type: ReasoningType,
472 pub problem_statement: String,
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub hints: Option<Vec<String>>,
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct PostReasoningEvent {
482 pub session_id: String,
483 pub reasoning_type: ReasoningType,
484 pub conclusion: String,
485 pub steps_count: usize,
486 pub success: bool,
487 #[serde(skip_serializing_if = "Option::is_none")]
488 pub error: Option<String>,
489}
490
491#[derive(Debug, Clone, Serialize, Deserialize)]
493#[serde(rename_all = "snake_case")]
494pub enum RateLimitType {
495 LlmTokenLimit,
496 LlmRequestLimit,
497 ApiRequestLimit,
498 ToolExecutionLimit,
499 Custom(String),
500}
501
502#[derive(Debug, Clone, Serialize, Deserialize)]
504pub struct OnRateLimitEvent {
505 pub session_id: String,
506 pub limit_type: RateLimitType,
508 pub retry_after_ms: u64,
510 pub current_usage: String,
512}
513
514#[derive(Debug, Clone, Serialize, Deserialize)]
516#[serde(rename_all = "snake_case")]
517pub enum ConfirmationType {
518 SafetyConfirm,
519 UserConfirm,
520 CostConfirm,
521 Custom(String),
522}
523
524#[derive(Debug, Clone, Serialize, Deserialize)]
526pub struct OnConfirmationEvent {
527 pub session_id: String,
528 pub confirmation_type: ConfirmationType,
530 pub message: String,
532 #[serde(skip_serializing_if = "Option::is_none")]
534 pub options: Option<Vec<String>>,
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize)]
539pub struct IntentDetectionEvent {
540 pub session_id: String,
541 pub prompt: String,
542 pub workspace: String,
543 #[serde(skip_serializing_if = "Option::is_none")]
545 pub language_hint: Option<String>,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize)]
550#[serde(tag = "event_type", content = "payload")]
551pub enum HookEvent {
552 #[serde(rename = "pre_tool_use")]
553 PreToolUse(PreToolUseEvent),
554 #[serde(rename = "post_tool_use")]
555 PostToolUse(PostToolUseEvent),
556 #[serde(rename = "permission_request")]
557 PermissionRequest(PermissionRequestEvent),
558 #[serde(rename = "pre_compact")]
559 PreCompact(PreCompactEvent),
560 #[serde(rename = "post_compact")]
561 PostCompact(PostCompactEvent),
562 #[serde(rename = "generate_start")]
563 GenerateStart(GenerateStartEvent),
564 #[serde(rename = "generate_end")]
565 GenerateEnd(GenerateEndEvent),
566 #[serde(rename = "session_start")]
567 SessionStart(SessionStartEvent),
568 #[serde(rename = "session_end")]
569 SessionEnd(SessionEndEvent),
570 #[serde(rename = "skill_load")]
571 SkillLoad(SkillLoadEvent),
572 #[serde(rename = "skill_unload")]
573 SkillUnload(SkillUnloadEvent),
574 #[serde(rename = "pre_prompt")]
575 PrePrompt(PrePromptEvent),
576 #[serde(rename = "post_response")]
577 PostResponse(PostResponseEvent),
578 #[serde(rename = "on_error")]
579 OnError(OnErrorEvent),
580 #[serde(rename = "pre_context_perception")]
582 PreContextPerception(PreContextPerceptionEvent),
583 #[serde(rename = "post_context_perception")]
584 PostContextPerception(PostContextPerceptionEvent),
585 #[serde(rename = "on_success")]
586 OnSuccess(OnSuccessEvent),
587 #[serde(rename = "pre_memory_recall")]
588 PreMemoryRecall(PreMemoryRecallEvent),
589 #[serde(rename = "post_memory_recall")]
590 PostMemoryRecall(PostMemoryRecallEvent),
591 #[serde(rename = "pre_planning")]
592 PrePlanning(PrePlanningEvent),
593 #[serde(rename = "post_planning")]
594 PostPlanning(PostPlanningEvent),
595 #[serde(rename = "pre_reasoning")]
596 PreReasoning(PreReasoningEvent),
597 #[serde(rename = "post_reasoning")]
598 PostReasoning(PostReasoningEvent),
599 #[serde(rename = "on_rate_limit")]
600 OnRateLimit(OnRateLimitEvent),
601 #[serde(rename = "on_confirmation")]
602 OnConfirmation(OnConfirmationEvent),
603 #[serde(rename = "intent_detection")]
604 IntentDetection(IntentDetectionEvent),
605}
606
607impl HookEvent {
608 pub fn event_type(&self) -> HookEventType {
610 match self {
611 HookEvent::PreToolUse(_) => HookEventType::PreToolUse,
612 HookEvent::PostToolUse(_) => HookEventType::PostToolUse,
613 HookEvent::PermissionRequest(_) => HookEventType::PermissionRequest,
614 HookEvent::PreCompact(_) => HookEventType::PreCompact,
615 HookEvent::PostCompact(_) => HookEventType::PostCompact,
616 HookEvent::GenerateStart(_) => HookEventType::GenerateStart,
617 HookEvent::GenerateEnd(_) => HookEventType::GenerateEnd,
618 HookEvent::SessionStart(_) => HookEventType::SessionStart,
619 HookEvent::SessionEnd(_) => HookEventType::SessionEnd,
620 HookEvent::SkillLoad(_) => HookEventType::SkillLoad,
621 HookEvent::SkillUnload(_) => HookEventType::SkillUnload,
622 HookEvent::PrePrompt(_) => HookEventType::PrePrompt,
623 HookEvent::PostResponse(_) => HookEventType::PostResponse,
624 HookEvent::OnError(_) => HookEventType::OnError,
625 HookEvent::PreContextPerception(_) => HookEventType::PreContextPerception,
627 HookEvent::PostContextPerception(_) => HookEventType::PostContextPerception,
628 HookEvent::OnSuccess(_) => HookEventType::OnSuccess,
629 HookEvent::PreMemoryRecall(_) => HookEventType::PreMemoryRecall,
630 HookEvent::PostMemoryRecall(_) => HookEventType::PostMemoryRecall,
631 HookEvent::PrePlanning(_) => HookEventType::PrePlanning,
632 HookEvent::PostPlanning(_) => HookEventType::PostPlanning,
633 HookEvent::PreReasoning(_) => HookEventType::PreReasoning,
634 HookEvent::PostReasoning(_) => HookEventType::PostReasoning,
635 HookEvent::OnRateLimit(_) => HookEventType::OnRateLimit,
636 HookEvent::OnConfirmation(_) => HookEventType::OnConfirmation,
637 HookEvent::IntentDetection(_) => HookEventType::IntentDetection,
638 }
639 }
640
641 pub fn session_id(&self) -> &str {
643 match self {
644 HookEvent::PreToolUse(e) => &e.session_id,
645 HookEvent::PostToolUse(e) => &e.session_id,
646 HookEvent::PermissionRequest(e) => &e.session_id,
647 HookEvent::PreCompact(e) => &e.session_id,
648 HookEvent::PostCompact(e) => &e.session_id,
649 HookEvent::GenerateStart(e) => &e.session_id,
650 HookEvent::GenerateEnd(e) => &e.session_id,
651 HookEvent::SessionStart(e) => &e.session_id,
652 HookEvent::SessionEnd(e) => &e.session_id,
653 HookEvent::PrePrompt(e) => &e.session_id,
654 HookEvent::PostResponse(e) => &e.session_id,
655 HookEvent::OnError(e) => &e.session_id,
656 HookEvent::PreContextPerception(e) => &e.session_id,
658 HookEvent::PostContextPerception(e) => &e.session_id,
659 HookEvent::OnSuccess(e) => &e.session_id,
660 HookEvent::PreMemoryRecall(e) => &e.session_id,
661 HookEvent::PostMemoryRecall(e) => &e.session_id,
662 HookEvent::PrePlanning(e) => &e.session_id,
663 HookEvent::PostPlanning(e) => &e.session_id,
664 HookEvent::PreReasoning(e) => &e.session_id,
665 HookEvent::PostReasoning(e) => &e.session_id,
666 HookEvent::OnRateLimit(e) => &e.session_id,
667 HookEvent::OnConfirmation(e) => &e.session_id,
668 HookEvent::IntentDetection(e) => &e.session_id,
669 HookEvent::SkillLoad(_) => "",
671 HookEvent::SkillUnload(_) => "",
672 }
673 }
674
675 pub fn tool_name(&self) -> Option<&str> {
677 match self {
678 HookEvent::PreToolUse(e) => Some(&e.tool),
679 HookEvent::PostToolUse(e) => Some(&e.tool),
680 _ => None,
681 }
682 }
683
684 pub fn tool_args(&self) -> Option<&serde_json::Value> {
686 match self {
687 HookEvent::PreToolUse(e) => Some(&e.args),
688 HookEvent::PostToolUse(e) => Some(&e.args),
689 _ => None,
690 }
691 }
692
693 pub fn skill_name(&self) -> Option<&str> {
695 match self {
696 HookEvent::SkillLoad(e) => Some(&e.skill_name),
697 HookEvent::SkillUnload(e) => Some(&e.skill_name),
698 _ => None,
699 }
700 }
701}
702
703#[cfg(test)]
704mod tests {
705 use super::*;
706
707 #[test]
708 fn test_hook_event_type_display() {
709 assert_eq!(HookEventType::PreToolUse.to_string(), "pre_tool_use");
710 assert_eq!(HookEventType::PostToolUse.to_string(), "post_tool_use");
711 assert_eq!(HookEventType::GenerateStart.to_string(), "generate_start");
712 assert_eq!(HookEventType::GenerateEnd.to_string(), "generate_end");
713 assert_eq!(HookEventType::SessionStart.to_string(), "session_start");
714 assert_eq!(HookEventType::SessionEnd.to_string(), "session_end");
715 assert_eq!(HookEventType::SkillLoad.to_string(), "skill_load");
716 assert_eq!(HookEventType::SkillUnload.to_string(), "skill_unload");
717 }
718
719 #[test]
720 fn test_pre_tool_use_event() {
721 let event = PreToolUseEvent {
722 session_id: "session-1".to_string(),
723 tool: "Bash".to_string(),
724 args: serde_json::json!({"command": "echo hello"}),
725 working_directory: "/workspace".to_string(),
726 recent_tools: vec!["Read".to_string()],
727 };
728
729 assert_eq!(event.session_id, "session-1");
730 assert_eq!(event.tool, "Bash");
731 }
732
733 #[test]
734 fn test_post_tool_use_event() {
735 let event = PostToolUseEvent {
736 session_id: "session-1".to_string(),
737 tool: "Bash".to_string(),
738 args: serde_json::json!({"command": "echo hello"}),
739 result: ToolResultData {
740 success: true,
741 output: "hello\n".to_string(),
742 exit_code: Some(0),
743 duration_ms: 50,
744 },
745 };
746
747 assert!(event.result.success);
748 assert_eq!(event.result.exit_code, Some(0));
749 }
750
751 #[test]
752 fn test_hook_event_type() {
753 let pre_tool = HookEvent::PreToolUse(PreToolUseEvent {
754 session_id: "s1".to_string(),
755 tool: "Bash".to_string(),
756 args: serde_json::json!({}),
757 working_directory: "/".to_string(),
758 recent_tools: vec![],
759 });
760
761 assert_eq!(pre_tool.event_type(), HookEventType::PreToolUse);
762 assert_eq!(pre_tool.session_id(), "s1");
763 assert_eq!(pre_tool.tool_name(), Some("Bash"));
764 }
765
766 #[test]
767 fn test_hook_event_serialization() {
768 let event = HookEvent::PreToolUse(PreToolUseEvent {
769 session_id: "s1".to_string(),
770 tool: "Bash".to_string(),
771 args: serde_json::json!({"command": "ls"}),
772 working_directory: "/workspace".to_string(),
773 recent_tools: vec![],
774 });
775
776 let json = serde_json::to_string(&event).unwrap();
777 assert!(json.contains("pre_tool_use"));
778 assert!(json.contains("Bash"));
779
780 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
782 assert_eq!(parsed.event_type(), HookEventType::PreToolUse);
783 }
784
785 #[test]
786 fn test_generate_events() {
787 let start = GenerateStartEvent {
788 session_id: "s1".to_string(),
789 prompt: "Hello".to_string(),
790 system_prompt: Some("You are helpful".to_string()),
791 model_provider: "anthropic".to_string(),
792 model_name: "claude-3".to_string(),
793 available_tools: vec!["Bash".to_string(), "Read".to_string()],
794 };
795
796 let end = GenerateEndEvent {
797 session_id: "s1".to_string(),
798 prompt: "Hello".to_string(),
799 response_text: "Hi there!".to_string(),
800 tool_calls: vec![],
801 usage: TokenUsageInfo {
802 prompt_tokens: 10,
803 completion_tokens: 5,
804 total_tokens: 15,
805 },
806 duration_ms: 500,
807 };
808
809 assert_eq!(start.prompt, "Hello");
810 assert_eq!(end.response_text, "Hi there!");
811 assert_eq!(end.usage.total_tokens, 15);
812 }
813
814 #[test]
815 fn test_session_events() {
816 let start = SessionStartEvent {
817 session_id: "s1".to_string(),
818 system_prompt: Some("System".to_string()),
819 model_provider: "anthropic".to_string(),
820 model_name: "claude-3".to_string(),
821 };
822
823 let end = SessionEndEvent {
824 session_id: "s1".to_string(),
825 total_tokens: 1000,
826 total_tool_calls: 5,
827 duration_ms: 60000,
828 };
829
830 let start_event = HookEvent::SessionStart(start);
831 let end_event = HookEvent::SessionEnd(end);
832
833 assert_eq!(start_event.event_type(), HookEventType::SessionStart);
834 assert_eq!(end_event.event_type(), HookEventType::SessionEnd);
835 assert!(start_event.tool_name().is_none());
836 }
837
838 #[test]
839 fn test_skill_load_event() {
840 let event = SkillLoadEvent {
841 skill_name: "test-skill".to_string(),
842 tool_names: vec!["tool1".to_string(), "tool2".to_string()],
843 version: Some("1.0.0".to_string()),
844 description: Some("A test skill".to_string()),
845 loaded_at: 1234567890,
846 };
847
848 assert_eq!(event.skill_name, "test-skill");
849 assert_eq!(event.tool_names.len(), 2);
850 assert_eq!(event.version, Some("1.0.0".to_string()));
851 assert_eq!(event.loaded_at, 1234567890);
852 }
853
854 #[test]
855 fn test_skill_unload_event() {
856 let event = SkillUnloadEvent {
857 skill_name: "test-skill".to_string(),
858 tool_names: vec!["tool1".to_string(), "tool2".to_string()],
859 duration_ms: 60000,
860 };
861
862 assert_eq!(event.skill_name, "test-skill");
863 assert_eq!(event.tool_names.len(), 2);
864 assert_eq!(event.duration_ms, 60000);
865 }
866
867 #[test]
868 fn test_hook_event_skill_name() {
869 let load_event = HookEvent::SkillLoad(SkillLoadEvent {
870 skill_name: "my-skill".to_string(),
871 tool_names: vec!["tool1".to_string()],
872 version: None,
873 description: None,
874 loaded_at: 0,
875 });
876
877 let unload_event = HookEvent::SkillUnload(SkillUnloadEvent {
878 skill_name: "my-skill".to_string(),
879 tool_names: vec!["tool1".to_string()],
880 duration_ms: 1000,
881 });
882
883 assert_eq!(load_event.event_type(), HookEventType::SkillLoad);
884 assert_eq!(load_event.skill_name(), Some("my-skill"));
885 assert_eq!(load_event.session_id(), ""); assert_eq!(unload_event.event_type(), HookEventType::SkillUnload);
888 assert_eq!(unload_event.skill_name(), Some("my-skill"));
889 assert_eq!(unload_event.session_id(), ""); let pre_tool = HookEvent::PreToolUse(PreToolUseEvent {
893 session_id: "s1".to_string(),
894 tool: "Bash".to_string(),
895 args: serde_json::json!({}),
896 working_directory: "/".to_string(),
897 recent_tools: vec![],
898 });
899 assert!(pre_tool.skill_name().is_none());
900 }
901
902 #[test]
903 fn test_skill_event_serialization() {
904 let event = HookEvent::SkillLoad(SkillLoadEvent {
905 skill_name: "test-skill".to_string(),
906 tool_names: vec!["tool1".to_string()],
907 version: Some("1.0.0".to_string()),
908 description: None,
909 loaded_at: 1234567890,
910 });
911
912 let json = serde_json::to_string(&event).unwrap();
913 assert!(json.contains("skill_load"));
914 assert!(json.contains("test-skill"));
915 assert!(json.contains("1.0.0"));
916
917 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
918 assert_eq!(parsed.event_type(), HookEventType::SkillLoad);
919 assert_eq!(parsed.skill_name(), Some("test-skill"));
920 }
921
922 #[test]
923 fn test_hook_event_type_display_new_variants() {
924 assert_eq!(HookEventType::PrePrompt.to_string(), "pre_prompt");
925 assert_eq!(HookEventType::PostResponse.to_string(), "post_response");
926 assert_eq!(HookEventType::OnError.to_string(), "on_error");
927 }
928
929 #[test]
930 fn test_pre_prompt_event() {
931 let event = PrePromptEvent {
932 session_id: "s1".to_string(),
933 prompt: "Fix the bug".to_string(),
934 system_prompt: Some("You are helpful".to_string()),
935 message_count: 5,
936 };
937
938 assert_eq!(event.session_id, "s1");
939 assert_eq!(event.prompt, "Fix the bug");
940 assert_eq!(event.message_count, 5);
941
942 let hook_event = HookEvent::PrePrompt(event);
943 assert_eq!(hook_event.event_type(), HookEventType::PrePrompt);
944 assert_eq!(hook_event.session_id(), "s1");
945 assert!(hook_event.tool_name().is_none());
946 assert!(hook_event.skill_name().is_none());
947 }
948
949 #[test]
950 fn test_post_response_event() {
951 let event = PostResponseEvent {
952 session_id: "s1".to_string(),
953 response_text: "Done!".to_string(),
954 tool_calls_count: 3,
955 usage: TokenUsageInfo {
956 prompt_tokens: 100,
957 completion_tokens: 50,
958 total_tokens: 150,
959 },
960 duration_ms: 2000,
961 };
962
963 assert_eq!(event.response_text, "Done!");
964 assert_eq!(event.tool_calls_count, 3);
965 assert_eq!(event.usage.total_tokens, 150);
966
967 let hook_event = HookEvent::PostResponse(event);
968 assert_eq!(hook_event.event_type(), HookEventType::PostResponse);
969 assert_eq!(hook_event.session_id(), "s1");
970 }
971
972 #[test]
973 fn test_on_error_event() {
974 let event = OnErrorEvent {
975 session_id: "s1".to_string(),
976 error_type: ErrorType::ToolFailure,
977 error_message: "Command failed with exit code 1".to_string(),
978 context: serde_json::json!({"tool": "Bash", "command": "false"}),
979 };
980
981 assert_eq!(event.error_type.to_string(), "tool_failure");
982 assert_eq!(event.error_message, "Command failed with exit code 1");
983
984 let hook_event = HookEvent::OnError(event);
985 assert_eq!(hook_event.event_type(), HookEventType::OnError);
986 assert_eq!(hook_event.session_id(), "s1");
987 }
988
989 #[test]
990 fn test_error_type_display() {
991 assert_eq!(ErrorType::ToolFailure.to_string(), "tool_failure");
992 assert_eq!(ErrorType::LlmFailure.to_string(), "llm_failure");
993 assert_eq!(ErrorType::PermissionDenied.to_string(), "permission_denied");
994 assert_eq!(ErrorType::Timeout.to_string(), "timeout");
995 assert_eq!(ErrorType::Other.to_string(), "other");
996 }
997
998 #[test]
999 fn test_new_event_serialization() {
1000 let event = HookEvent::PrePrompt(PrePromptEvent {
1002 session_id: "s1".to_string(),
1003 prompt: "Hello".to_string(),
1004 system_prompt: None,
1005 message_count: 0,
1006 });
1007 let json = serde_json::to_string(&event).unwrap();
1008 assert!(json.contains("pre_prompt"));
1009 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
1010 assert_eq!(parsed.event_type(), HookEventType::PrePrompt);
1011
1012 let event = HookEvent::PostResponse(PostResponseEvent {
1014 session_id: "s1".to_string(),
1015 response_text: "Hi".to_string(),
1016 tool_calls_count: 0,
1017 usage: TokenUsageInfo {
1018 prompt_tokens: 10,
1019 completion_tokens: 5,
1020 total_tokens: 15,
1021 },
1022 duration_ms: 100,
1023 });
1024 let json = serde_json::to_string(&event).unwrap();
1025 assert!(json.contains("post_response"));
1026 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
1027 assert_eq!(parsed.event_type(), HookEventType::PostResponse);
1028
1029 let event = HookEvent::OnError(OnErrorEvent {
1031 session_id: "s1".to_string(),
1032 error_type: ErrorType::LlmFailure,
1033 error_message: "API timeout".to_string(),
1034 context: serde_json::json!({}),
1035 });
1036 let json = serde_json::to_string(&event).unwrap();
1037 assert!(json.contains("on_error"));
1038 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
1039 assert_eq!(parsed.event_type(), HookEventType::OnError);
1040 }
1041}