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 GenerateStart,
17 GenerateEnd,
19 SessionStart,
21 SessionEnd,
23 SkillLoad,
25 SkillUnload,
27 PrePrompt,
29 PostResponse,
31 OnError,
33 PreContextPerception,
36 PostContextPerception,
38
39 OnSuccess,
41
42 PreMemoryRecall,
44 PostMemoryRecall,
46
47 PrePlanning,
49 PostPlanning,
51
52 PreReasoning,
54 PostReasoning,
56
57 OnRateLimit,
59
60 OnConfirmation,
62
63 IntentDetection,
65}
66
67impl std::fmt::Display for HookEventType {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 match self {
70 HookEventType::PreToolUse => write!(f, "pre_tool_use"),
71 HookEventType::PostToolUse => write!(f, "post_tool_use"),
72 HookEventType::GenerateStart => write!(f, "generate_start"),
73 HookEventType::GenerateEnd => write!(f, "generate_end"),
74 HookEventType::SessionStart => write!(f, "session_start"),
75 HookEventType::SessionEnd => write!(f, "session_end"),
76 HookEventType::SkillLoad => write!(f, "skill_load"),
77 HookEventType::SkillUnload => write!(f, "skill_unload"),
78 HookEventType::PrePrompt => write!(f, "pre_prompt"),
79 HookEventType::PostResponse => write!(f, "post_response"),
80 HookEventType::OnError => write!(f, "on_error"),
81 HookEventType::PreContextPerception => write!(f, "pre_context_perception"),
83 HookEventType::PostContextPerception => write!(f, "post_context_perception"),
84 HookEventType::OnSuccess => write!(f, "on_success"),
85 HookEventType::PreMemoryRecall => write!(f, "pre_memory_recall"),
86 HookEventType::PostMemoryRecall => write!(f, "post_memory_recall"),
87 HookEventType::PrePlanning => write!(f, "pre_planning"),
88 HookEventType::PostPlanning => write!(f, "post_planning"),
89 HookEventType::PreReasoning => write!(f, "pre_reasoning"),
90 HookEventType::PostReasoning => write!(f, "post_reasoning"),
91 HookEventType::OnRateLimit => write!(f, "on_rate_limit"),
92 HookEventType::OnConfirmation => write!(f, "on_confirmation"),
93 HookEventType::IntentDetection => write!(f, "intent_detection"),
94 }
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct ToolResultData {
101 pub success: bool,
103 pub output: String,
105 pub exit_code: Option<i32>,
107 pub duration_ms: u64,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct PreToolUseEvent {
114 pub session_id: String,
116 pub tool: String,
118 pub args: serde_json::Value,
120 pub working_directory: String,
122 pub recent_tools: Vec<String>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct PostToolUseEvent {
129 pub session_id: String,
131 pub tool: String,
133 pub args: serde_json::Value,
135 pub result: ToolResultData,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct GenerateStartEvent {
142 pub session_id: String,
144 pub prompt: String,
146 pub system_prompt: Option<String>,
148 pub model_provider: String,
150 pub model_name: String,
152 pub available_tools: Vec<String>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct GenerateEndEvent {
159 pub session_id: String,
161 pub prompt: String,
163 pub response_text: String,
165 pub tool_calls: Vec<ToolCallInfo>,
167 pub usage: TokenUsageInfo,
169 pub duration_ms: u64,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct ToolCallInfo {
176 pub name: String,
178 pub args: serde_json::Value,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct TokenUsageInfo {
185 pub prompt_tokens: i32,
187 pub completion_tokens: i32,
189 pub total_tokens: i32,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct SessionStartEvent {
196 pub session_id: String,
198 pub system_prompt: Option<String>,
200 pub model_provider: String,
202 pub model_name: String,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct SessionEndEvent {
208 pub session_id: String,
210 pub total_tokens: i32,
212 pub total_tool_calls: i32,
214 pub duration_ms: u64,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct SkillLoadEvent {
221 pub skill_name: String,
223 pub tool_names: Vec<String>,
225 pub version: Option<String>,
227 pub description: Option<String>,
229 pub loaded_at: i64,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct SkillUnloadEvent {
236 pub skill_name: String,
238 pub tool_names: Vec<String>,
240 pub duration_ms: u64,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct PrePromptEvent {
247 pub session_id: String,
249 pub prompt: String,
251 pub system_prompt: Option<String>,
253 pub message_count: usize,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct PostResponseEvent {
260 pub session_id: String,
262 pub response_text: String,
264 pub tool_calls_count: usize,
266 pub usage: TokenUsageInfo,
268 pub duration_ms: u64,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
274#[serde(rename_all = "snake_case")]
275pub enum ErrorType {
276 ToolFailure,
278 LlmFailure,
280 PermissionDenied,
282 Timeout,
284 Other,
286}
287
288impl std::fmt::Display for ErrorType {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 match self {
291 ErrorType::ToolFailure => write!(f, "tool_failure"),
292 ErrorType::LlmFailure => write!(f, "llm_failure"),
293 ErrorType::PermissionDenied => write!(f, "permission_denied"),
294 ErrorType::Timeout => write!(f, "timeout"),
295 ErrorType::Other => write!(f, "other"),
296 }
297 }
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct OnErrorEvent {
303 pub session_id: String,
305 pub error_type: ErrorType,
307 pub error_message: String,
309 pub context: serde_json::Value,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct PreContextPerceptionEvent {
320 pub session_id: String,
321 pub intent: String,
322 pub target_type: String,
323 pub target_name: String,
324 pub domain: String,
325 pub query: Option<String>,
326 pub working_directory: String,
327 pub urgency: String,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct PostContextPerceptionEvent {
333 pub session_id: String,
334 pub intent: String,
335 pub target_type: String,
336 pub success: bool,
337 pub facts_retrieved: usize,
338 pub files_retrieved: usize,
339 #[serde(skip_serializing_if = "Option::is_none")]
340 pub error: Option<String>,
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct OnSuccessEvent {
346 pub session_id: String,
348 pub action_type: String,
350 pub action_summary: String,
352 pub duration_ms: u64,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct PreMemoryRecallEvent {
359 pub session_id: String,
360 pub query: String,
362 pub memory_type: String,
364 pub max_results: usize,
366 pub working_directory: String,
368}
369
370#[derive(Debug, Clone, Serialize, Deserialize)]
372pub struct PostMemoryRecallEvent {
373 pub session_id: String,
374 pub query: String,
375 pub memory_type: String,
376 pub facts_retrieved: usize,
377 pub success: bool,
378 #[serde(skip_serializing_if = "Option::is_none")]
379 pub error: Option<String>,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
384#[serde(rename_all = "snake_case")]
385pub enum PlanningStrategy {
386 None,
387 StepByStep,
388 TreeOfThoughts,
389 GraphPlanning,
390 Custom(String),
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct PrePlanningEvent {
396 pub session_id: String,
397 pub task_description: String,
399 pub available_strategies: Vec<PlanningStrategy>,
401 #[serde(skip_serializing_if = "Option::is_none")]
403 pub constraints: Option<serde_json::Value>,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct PostPlanningEvent {
409 pub session_id: String,
410 pub task_description: String,
411 pub strategy_used: PlanningStrategy,
412 pub subtasks: Vec<String>,
414 pub success: bool,
415 #[serde(skip_serializing_if = "Option::is_none")]
416 pub error: Option<String>,
417}
418
419#[derive(Debug, Clone, Serialize, Deserialize)]
421#[serde(rename_all = "snake_case")]
422pub enum ReasoningType {
423 ChainOfThought,
424 TreeOfThoughts,
425 ReAct,
426 Reflexion,
427 Other(String),
428}
429
430#[derive(Debug, Clone, Serialize, Deserialize)]
432pub struct PreReasoningEvent {
433 pub session_id: String,
434 pub reasoning_type: ReasoningType,
436 pub problem_statement: String,
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub hints: Option<Vec<String>>,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize)]
445pub struct PostReasoningEvent {
446 pub session_id: String,
447 pub reasoning_type: ReasoningType,
448 pub conclusion: String,
449 pub steps_count: usize,
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 RateLimitType {
459 LlmTokenLimit,
460 LlmRequestLimit,
461 ApiRequestLimit,
462 ToolExecutionLimit,
463 Custom(String),
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct OnRateLimitEvent {
469 pub session_id: String,
470 pub limit_type: RateLimitType,
472 pub retry_after_ms: u64,
474 pub current_usage: String,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize)]
480#[serde(rename_all = "snake_case")]
481pub enum ConfirmationType {
482 SafetyConfirm,
483 UserConfirm,
484 CostConfirm,
485 Custom(String),
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct OnConfirmationEvent {
491 pub session_id: String,
492 pub confirmation_type: ConfirmationType,
494 pub message: String,
496 #[serde(skip_serializing_if = "Option::is_none")]
498 pub options: Option<Vec<String>>,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct IntentDetectionEvent {
504 pub session_id: String,
505 pub prompt: String,
506 pub workspace: String,
507 #[serde(skip_serializing_if = "Option::is_none")]
509 pub language_hint: Option<String>,
510}
511
512#[derive(Debug, Clone, Serialize, Deserialize)]
514#[serde(tag = "event_type", content = "payload")]
515pub enum HookEvent {
516 #[serde(rename = "pre_tool_use")]
517 PreToolUse(PreToolUseEvent),
518 #[serde(rename = "post_tool_use")]
519 PostToolUse(PostToolUseEvent),
520 #[serde(rename = "generate_start")]
521 GenerateStart(GenerateStartEvent),
522 #[serde(rename = "generate_end")]
523 GenerateEnd(GenerateEndEvent),
524 #[serde(rename = "session_start")]
525 SessionStart(SessionStartEvent),
526 #[serde(rename = "session_end")]
527 SessionEnd(SessionEndEvent),
528 #[serde(rename = "skill_load")]
529 SkillLoad(SkillLoadEvent),
530 #[serde(rename = "skill_unload")]
531 SkillUnload(SkillUnloadEvent),
532 #[serde(rename = "pre_prompt")]
533 PrePrompt(PrePromptEvent),
534 #[serde(rename = "post_response")]
535 PostResponse(PostResponseEvent),
536 #[serde(rename = "on_error")]
537 OnError(OnErrorEvent),
538 #[serde(rename = "pre_context_perception")]
540 PreContextPerception(PreContextPerceptionEvent),
541 #[serde(rename = "post_context_perception")]
542 PostContextPerception(PostContextPerceptionEvent),
543 #[serde(rename = "on_success")]
544 OnSuccess(OnSuccessEvent),
545 #[serde(rename = "pre_memory_recall")]
546 PreMemoryRecall(PreMemoryRecallEvent),
547 #[serde(rename = "post_memory_recall")]
548 PostMemoryRecall(PostMemoryRecallEvent),
549 #[serde(rename = "pre_planning")]
550 PrePlanning(PrePlanningEvent),
551 #[serde(rename = "post_planning")]
552 PostPlanning(PostPlanningEvent),
553 #[serde(rename = "pre_reasoning")]
554 PreReasoning(PreReasoningEvent),
555 #[serde(rename = "post_reasoning")]
556 PostReasoning(PostReasoningEvent),
557 #[serde(rename = "on_rate_limit")]
558 OnRateLimit(OnRateLimitEvent),
559 #[serde(rename = "on_confirmation")]
560 OnConfirmation(OnConfirmationEvent),
561 #[serde(rename = "intent_detection")]
562 IntentDetection(IntentDetectionEvent),
563}
564
565impl HookEvent {
566 pub fn event_type(&self) -> HookEventType {
568 match self {
569 HookEvent::PreToolUse(_) => HookEventType::PreToolUse,
570 HookEvent::PostToolUse(_) => HookEventType::PostToolUse,
571 HookEvent::GenerateStart(_) => HookEventType::GenerateStart,
572 HookEvent::GenerateEnd(_) => HookEventType::GenerateEnd,
573 HookEvent::SessionStart(_) => HookEventType::SessionStart,
574 HookEvent::SessionEnd(_) => HookEventType::SessionEnd,
575 HookEvent::SkillLoad(_) => HookEventType::SkillLoad,
576 HookEvent::SkillUnload(_) => HookEventType::SkillUnload,
577 HookEvent::PrePrompt(_) => HookEventType::PrePrompt,
578 HookEvent::PostResponse(_) => HookEventType::PostResponse,
579 HookEvent::OnError(_) => HookEventType::OnError,
580 HookEvent::PreContextPerception(_) => HookEventType::PreContextPerception,
582 HookEvent::PostContextPerception(_) => HookEventType::PostContextPerception,
583 HookEvent::OnSuccess(_) => HookEventType::OnSuccess,
584 HookEvent::PreMemoryRecall(_) => HookEventType::PreMemoryRecall,
585 HookEvent::PostMemoryRecall(_) => HookEventType::PostMemoryRecall,
586 HookEvent::PrePlanning(_) => HookEventType::PrePlanning,
587 HookEvent::PostPlanning(_) => HookEventType::PostPlanning,
588 HookEvent::PreReasoning(_) => HookEventType::PreReasoning,
589 HookEvent::PostReasoning(_) => HookEventType::PostReasoning,
590 HookEvent::OnRateLimit(_) => HookEventType::OnRateLimit,
591 HookEvent::OnConfirmation(_) => HookEventType::OnConfirmation,
592 HookEvent::IntentDetection(_) => HookEventType::IntentDetection,
593 }
594 }
595
596 pub fn session_id(&self) -> &str {
598 match self {
599 HookEvent::PreToolUse(e) => &e.session_id,
600 HookEvent::PostToolUse(e) => &e.session_id,
601 HookEvent::GenerateStart(e) => &e.session_id,
602 HookEvent::GenerateEnd(e) => &e.session_id,
603 HookEvent::SessionStart(e) => &e.session_id,
604 HookEvent::SessionEnd(e) => &e.session_id,
605 HookEvent::PrePrompt(e) => &e.session_id,
606 HookEvent::PostResponse(e) => &e.session_id,
607 HookEvent::OnError(e) => &e.session_id,
608 HookEvent::PreContextPerception(e) => &e.session_id,
610 HookEvent::PostContextPerception(e) => &e.session_id,
611 HookEvent::OnSuccess(e) => &e.session_id,
612 HookEvent::PreMemoryRecall(e) => &e.session_id,
613 HookEvent::PostMemoryRecall(e) => &e.session_id,
614 HookEvent::PrePlanning(e) => &e.session_id,
615 HookEvent::PostPlanning(e) => &e.session_id,
616 HookEvent::PreReasoning(e) => &e.session_id,
617 HookEvent::PostReasoning(e) => &e.session_id,
618 HookEvent::OnRateLimit(e) => &e.session_id,
619 HookEvent::OnConfirmation(e) => &e.session_id,
620 HookEvent::IntentDetection(e) => &e.session_id,
621 HookEvent::SkillLoad(_) => "",
623 HookEvent::SkillUnload(_) => "",
624 }
625 }
626
627 pub fn tool_name(&self) -> Option<&str> {
629 match self {
630 HookEvent::PreToolUse(e) => Some(&e.tool),
631 HookEvent::PostToolUse(e) => Some(&e.tool),
632 _ => None,
633 }
634 }
635
636 pub fn tool_args(&self) -> Option<&serde_json::Value> {
638 match self {
639 HookEvent::PreToolUse(e) => Some(&e.args),
640 HookEvent::PostToolUse(e) => Some(&e.args),
641 _ => None,
642 }
643 }
644
645 pub fn skill_name(&self) -> Option<&str> {
647 match self {
648 HookEvent::SkillLoad(e) => Some(&e.skill_name),
649 HookEvent::SkillUnload(e) => Some(&e.skill_name),
650 _ => None,
651 }
652 }
653}
654
655#[cfg(test)]
656mod tests {
657 use super::*;
658
659 #[test]
660 fn test_hook_event_type_display() {
661 assert_eq!(HookEventType::PreToolUse.to_string(), "pre_tool_use");
662 assert_eq!(HookEventType::PostToolUse.to_string(), "post_tool_use");
663 assert_eq!(HookEventType::GenerateStart.to_string(), "generate_start");
664 assert_eq!(HookEventType::GenerateEnd.to_string(), "generate_end");
665 assert_eq!(HookEventType::SessionStart.to_string(), "session_start");
666 assert_eq!(HookEventType::SessionEnd.to_string(), "session_end");
667 assert_eq!(HookEventType::SkillLoad.to_string(), "skill_load");
668 assert_eq!(HookEventType::SkillUnload.to_string(), "skill_unload");
669 }
670
671 #[test]
672 fn test_pre_tool_use_event() {
673 let event = PreToolUseEvent {
674 session_id: "session-1".to_string(),
675 tool: "Bash".to_string(),
676 args: serde_json::json!({"command": "echo hello"}),
677 working_directory: "/workspace".to_string(),
678 recent_tools: vec!["Read".to_string()],
679 };
680
681 assert_eq!(event.session_id, "session-1");
682 assert_eq!(event.tool, "Bash");
683 }
684
685 #[test]
686 fn test_post_tool_use_event() {
687 let event = PostToolUseEvent {
688 session_id: "session-1".to_string(),
689 tool: "Bash".to_string(),
690 args: serde_json::json!({"command": "echo hello"}),
691 result: ToolResultData {
692 success: true,
693 output: "hello\n".to_string(),
694 exit_code: Some(0),
695 duration_ms: 50,
696 },
697 };
698
699 assert!(event.result.success);
700 assert_eq!(event.result.exit_code, Some(0));
701 }
702
703 #[test]
704 fn test_hook_event_type() {
705 let pre_tool = HookEvent::PreToolUse(PreToolUseEvent {
706 session_id: "s1".to_string(),
707 tool: "Bash".to_string(),
708 args: serde_json::json!({}),
709 working_directory: "/".to_string(),
710 recent_tools: vec![],
711 });
712
713 assert_eq!(pre_tool.event_type(), HookEventType::PreToolUse);
714 assert_eq!(pre_tool.session_id(), "s1");
715 assert_eq!(pre_tool.tool_name(), Some("Bash"));
716 }
717
718 #[test]
719 fn test_hook_event_serialization() {
720 let event = HookEvent::PreToolUse(PreToolUseEvent {
721 session_id: "s1".to_string(),
722 tool: "Bash".to_string(),
723 args: serde_json::json!({"command": "ls"}),
724 working_directory: "/workspace".to_string(),
725 recent_tools: vec![],
726 });
727
728 let json = serde_json::to_string(&event).unwrap();
729 assert!(json.contains("pre_tool_use"));
730 assert!(json.contains("Bash"));
731
732 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
734 assert_eq!(parsed.event_type(), HookEventType::PreToolUse);
735 }
736
737 #[test]
738 fn test_generate_events() {
739 let start = GenerateStartEvent {
740 session_id: "s1".to_string(),
741 prompt: "Hello".to_string(),
742 system_prompt: Some("You are helpful".to_string()),
743 model_provider: "anthropic".to_string(),
744 model_name: "claude-3".to_string(),
745 available_tools: vec!["Bash".to_string(), "Read".to_string()],
746 };
747
748 let end = GenerateEndEvent {
749 session_id: "s1".to_string(),
750 prompt: "Hello".to_string(),
751 response_text: "Hi there!".to_string(),
752 tool_calls: vec![],
753 usage: TokenUsageInfo {
754 prompt_tokens: 10,
755 completion_tokens: 5,
756 total_tokens: 15,
757 },
758 duration_ms: 500,
759 };
760
761 assert_eq!(start.prompt, "Hello");
762 assert_eq!(end.response_text, "Hi there!");
763 assert_eq!(end.usage.total_tokens, 15);
764 }
765
766 #[test]
767 fn test_session_events() {
768 let start = SessionStartEvent {
769 session_id: "s1".to_string(),
770 system_prompt: Some("System".to_string()),
771 model_provider: "anthropic".to_string(),
772 model_name: "claude-3".to_string(),
773 };
774
775 let end = SessionEndEvent {
776 session_id: "s1".to_string(),
777 total_tokens: 1000,
778 total_tool_calls: 5,
779 duration_ms: 60000,
780 };
781
782 let start_event = HookEvent::SessionStart(start);
783 let end_event = HookEvent::SessionEnd(end);
784
785 assert_eq!(start_event.event_type(), HookEventType::SessionStart);
786 assert_eq!(end_event.event_type(), HookEventType::SessionEnd);
787 assert!(start_event.tool_name().is_none());
788 }
789
790 #[test]
791 fn test_skill_load_event() {
792 let event = SkillLoadEvent {
793 skill_name: "test-skill".to_string(),
794 tool_names: vec!["tool1".to_string(), "tool2".to_string()],
795 version: Some("1.0.0".to_string()),
796 description: Some("A test skill".to_string()),
797 loaded_at: 1234567890,
798 };
799
800 assert_eq!(event.skill_name, "test-skill");
801 assert_eq!(event.tool_names.len(), 2);
802 assert_eq!(event.version, Some("1.0.0".to_string()));
803 assert_eq!(event.loaded_at, 1234567890);
804 }
805
806 #[test]
807 fn test_skill_unload_event() {
808 let event = SkillUnloadEvent {
809 skill_name: "test-skill".to_string(),
810 tool_names: vec!["tool1".to_string(), "tool2".to_string()],
811 duration_ms: 60000,
812 };
813
814 assert_eq!(event.skill_name, "test-skill");
815 assert_eq!(event.tool_names.len(), 2);
816 assert_eq!(event.duration_ms, 60000);
817 }
818
819 #[test]
820 fn test_hook_event_skill_name() {
821 let load_event = HookEvent::SkillLoad(SkillLoadEvent {
822 skill_name: "my-skill".to_string(),
823 tool_names: vec!["tool1".to_string()],
824 version: None,
825 description: None,
826 loaded_at: 0,
827 });
828
829 let unload_event = HookEvent::SkillUnload(SkillUnloadEvent {
830 skill_name: "my-skill".to_string(),
831 tool_names: vec!["tool1".to_string()],
832 duration_ms: 1000,
833 });
834
835 assert_eq!(load_event.event_type(), HookEventType::SkillLoad);
836 assert_eq!(load_event.skill_name(), Some("my-skill"));
837 assert_eq!(load_event.session_id(), ""); assert_eq!(unload_event.event_type(), HookEventType::SkillUnload);
840 assert_eq!(unload_event.skill_name(), Some("my-skill"));
841 assert_eq!(unload_event.session_id(), ""); let pre_tool = HookEvent::PreToolUse(PreToolUseEvent {
845 session_id: "s1".to_string(),
846 tool: "Bash".to_string(),
847 args: serde_json::json!({}),
848 working_directory: "/".to_string(),
849 recent_tools: vec![],
850 });
851 assert!(pre_tool.skill_name().is_none());
852 }
853
854 #[test]
855 fn test_skill_event_serialization() {
856 let event = HookEvent::SkillLoad(SkillLoadEvent {
857 skill_name: "test-skill".to_string(),
858 tool_names: vec!["tool1".to_string()],
859 version: Some("1.0.0".to_string()),
860 description: None,
861 loaded_at: 1234567890,
862 });
863
864 let json = serde_json::to_string(&event).unwrap();
865 assert!(json.contains("skill_load"));
866 assert!(json.contains("test-skill"));
867 assert!(json.contains("1.0.0"));
868
869 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
870 assert_eq!(parsed.event_type(), HookEventType::SkillLoad);
871 assert_eq!(parsed.skill_name(), Some("test-skill"));
872 }
873
874 #[test]
875 fn test_hook_event_type_display_new_variants() {
876 assert_eq!(HookEventType::PrePrompt.to_string(), "pre_prompt");
877 assert_eq!(HookEventType::PostResponse.to_string(), "post_response");
878 assert_eq!(HookEventType::OnError.to_string(), "on_error");
879 }
880
881 #[test]
882 fn test_pre_prompt_event() {
883 let event = PrePromptEvent {
884 session_id: "s1".to_string(),
885 prompt: "Fix the bug".to_string(),
886 system_prompt: Some("You are helpful".to_string()),
887 message_count: 5,
888 };
889
890 assert_eq!(event.session_id, "s1");
891 assert_eq!(event.prompt, "Fix the bug");
892 assert_eq!(event.message_count, 5);
893
894 let hook_event = HookEvent::PrePrompt(event);
895 assert_eq!(hook_event.event_type(), HookEventType::PrePrompt);
896 assert_eq!(hook_event.session_id(), "s1");
897 assert!(hook_event.tool_name().is_none());
898 assert!(hook_event.skill_name().is_none());
899 }
900
901 #[test]
902 fn test_post_response_event() {
903 let event = PostResponseEvent {
904 session_id: "s1".to_string(),
905 response_text: "Done!".to_string(),
906 tool_calls_count: 3,
907 usage: TokenUsageInfo {
908 prompt_tokens: 100,
909 completion_tokens: 50,
910 total_tokens: 150,
911 },
912 duration_ms: 2000,
913 };
914
915 assert_eq!(event.response_text, "Done!");
916 assert_eq!(event.tool_calls_count, 3);
917 assert_eq!(event.usage.total_tokens, 150);
918
919 let hook_event = HookEvent::PostResponse(event);
920 assert_eq!(hook_event.event_type(), HookEventType::PostResponse);
921 assert_eq!(hook_event.session_id(), "s1");
922 }
923
924 #[test]
925 fn test_on_error_event() {
926 let event = OnErrorEvent {
927 session_id: "s1".to_string(),
928 error_type: ErrorType::ToolFailure,
929 error_message: "Command failed with exit code 1".to_string(),
930 context: serde_json::json!({"tool": "Bash", "command": "false"}),
931 };
932
933 assert_eq!(event.error_type.to_string(), "tool_failure");
934 assert_eq!(event.error_message, "Command failed with exit code 1");
935
936 let hook_event = HookEvent::OnError(event);
937 assert_eq!(hook_event.event_type(), HookEventType::OnError);
938 assert_eq!(hook_event.session_id(), "s1");
939 }
940
941 #[test]
942 fn test_error_type_display() {
943 assert_eq!(ErrorType::ToolFailure.to_string(), "tool_failure");
944 assert_eq!(ErrorType::LlmFailure.to_string(), "llm_failure");
945 assert_eq!(ErrorType::PermissionDenied.to_string(), "permission_denied");
946 assert_eq!(ErrorType::Timeout.to_string(), "timeout");
947 assert_eq!(ErrorType::Other.to_string(), "other");
948 }
949
950 #[test]
951 fn test_new_event_serialization() {
952 let event = HookEvent::PrePrompt(PrePromptEvent {
954 session_id: "s1".to_string(),
955 prompt: "Hello".to_string(),
956 system_prompt: None,
957 message_count: 0,
958 });
959 let json = serde_json::to_string(&event).unwrap();
960 assert!(json.contains("pre_prompt"));
961 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
962 assert_eq!(parsed.event_type(), HookEventType::PrePrompt);
963
964 let event = HookEvent::PostResponse(PostResponseEvent {
966 session_id: "s1".to_string(),
967 response_text: "Hi".to_string(),
968 tool_calls_count: 0,
969 usage: TokenUsageInfo {
970 prompt_tokens: 10,
971 completion_tokens: 5,
972 total_tokens: 15,
973 },
974 duration_ms: 100,
975 });
976 let json = serde_json::to_string(&event).unwrap();
977 assert!(json.contains("post_response"));
978 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
979 assert_eq!(parsed.event_type(), HookEventType::PostResponse);
980
981 let event = HookEvent::OnError(OnErrorEvent {
983 session_id: "s1".to_string(),
984 error_type: ErrorType::LlmFailure,
985 error_message: "API timeout".to_string(),
986 context: serde_json::json!({}),
987 });
988 let json = serde_json::to_string(&event).unwrap();
989 assert!(json.contains("on_error"));
990 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
991 assert_eq!(parsed.event_type(), HookEventType::OnError);
992 }
993}