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 PreRunControl,
73 PostRunControl,
76}
77
78impl std::fmt::Display for HookEventType {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 match self {
81 HookEventType::PreToolUse => write!(f, "pre_tool_use"),
82 HookEventType::PostToolUse => write!(f, "post_tool_use"),
83 HookEventType::PermissionRequest => write!(f, "permission_request"),
84 HookEventType::PreCompact => write!(f, "pre_compact"),
85 HookEventType::PostCompact => write!(f, "post_compact"),
86 HookEventType::GenerateStart => write!(f, "generate_start"),
87 HookEventType::GenerateEnd => write!(f, "generate_end"),
88 HookEventType::SessionStart => write!(f, "session_start"),
89 HookEventType::SessionEnd => write!(f, "session_end"),
90 HookEventType::SkillLoad => write!(f, "skill_load"),
91 HookEventType::SkillUnload => write!(f, "skill_unload"),
92 HookEventType::PrePrompt => write!(f, "pre_prompt"),
93 HookEventType::PostResponse => write!(f, "post_response"),
94 HookEventType::OnError => write!(f, "on_error"),
95 HookEventType::PreContextPerception => write!(f, "pre_context_perception"),
97 HookEventType::PostContextPerception => write!(f, "post_context_perception"),
98 HookEventType::OnSuccess => write!(f, "on_success"),
99 HookEventType::PreMemoryRecall => write!(f, "pre_memory_recall"),
100 HookEventType::PostMemoryRecall => write!(f, "post_memory_recall"),
101 HookEventType::PrePlanning => write!(f, "pre_planning"),
102 HookEventType::PostPlanning => write!(f, "post_planning"),
103 HookEventType::PreReasoning => write!(f, "pre_reasoning"),
104 HookEventType::PostReasoning => write!(f, "post_reasoning"),
105 HookEventType::OnRateLimit => write!(f, "on_rate_limit"),
106 HookEventType::OnConfirmation => write!(f, "on_confirmation"),
107 HookEventType::IntentDetection => write!(f, "intent_detection"),
108 HookEventType::PreRunControl => write!(f, "pre_run_control"),
109 HookEventType::PostRunControl => write!(f, "post_run_control"),
110 }
111 }
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct ToolResultData {
117 pub success: bool,
119 pub output: String,
121 pub exit_code: Option<i32>,
123 pub duration_ms: u64,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct PreToolUseEvent {
130 pub session_id: String,
132 pub tool: String,
134 pub args: serde_json::Value,
136 pub working_directory: String,
138 pub recent_tools: Vec<String>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct PostToolUseEvent {
145 pub session_id: String,
147 pub tool: String,
149 pub args: serde_json::Value,
151 pub result: ToolResultData,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct PermissionRequestEvent {
158 pub session_id: String,
159 pub tool_id: String,
160 pub tool: String,
161 pub args: serde_json::Value,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct PreCompactEvent {
167 pub session_id: String,
168 pub message_count: usize,
169 pub used_tokens: usize,
170 pub max_tokens: usize,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct PostCompactEvent {
176 pub session_id: String,
177 pub message_count_before: usize,
178 pub message_count_after: usize,
179 pub summary_generated: bool,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct GenerateStartEvent {
185 pub session_id: String,
187 pub prompt: String,
189 pub system_prompt: Option<String>,
191 pub model_provider: String,
193 pub model_name: String,
195 pub available_tools: Vec<String>,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct GenerateEndEvent {
202 pub session_id: String,
204 pub prompt: String,
206 pub response_text: String,
208 pub tool_calls: Vec<ToolCallInfo>,
210 pub usage: TokenUsageInfo,
212 pub duration_ms: u64,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct ToolCallInfo {
219 pub name: String,
221 pub args: serde_json::Value,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct TokenUsageInfo {
228 pub prompt_tokens: i32,
230 pub completion_tokens: i32,
232 pub total_tokens: i32,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct SessionStartEvent {
239 pub session_id: String,
241 pub system_prompt: Option<String>,
243 pub model_provider: String,
245 pub model_name: String,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct SessionEndEvent {
251 pub session_id: String,
253 pub total_tokens: i32,
255 pub total_tool_calls: i32,
257 pub duration_ms: u64,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct SkillLoadEvent {
264 pub skill_name: String,
266 pub tool_names: Vec<String>,
268 pub version: Option<String>,
270 pub description: Option<String>,
272 pub loaded_at: i64,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct SkillUnloadEvent {
279 pub skill_name: String,
281 pub tool_names: Vec<String>,
283 pub duration_ms: u64,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct PrePromptEvent {
290 pub session_id: String,
292 pub prompt: String,
294 pub system_prompt: Option<String>,
296 pub message_count: usize,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct PostResponseEvent {
303 pub session_id: String,
305 pub response_text: String,
307 pub tool_calls_count: usize,
309 pub usage: TokenUsageInfo,
311 pub duration_ms: u64,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
317#[serde(rename_all = "snake_case")]
318pub enum ErrorType {
319 ToolFailure,
321 LlmFailure,
323 PermissionDenied,
325 Timeout,
327 Other,
329}
330
331impl std::fmt::Display for ErrorType {
332 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333 match self {
334 ErrorType::ToolFailure => write!(f, "tool_failure"),
335 ErrorType::LlmFailure => write!(f, "llm_failure"),
336 ErrorType::PermissionDenied => write!(f, "permission_denied"),
337 ErrorType::Timeout => write!(f, "timeout"),
338 ErrorType::Other => write!(f, "other"),
339 }
340 }
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct OnErrorEvent {
346 pub session_id: String,
348 pub error_type: ErrorType,
350 pub error_message: String,
352 pub context: serde_json::Value,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct PreContextPerceptionEvent {
363 pub session_id: String,
364 pub intent: String,
365 pub target_type: String,
366 pub target_name: String,
367 pub domain: String,
368 pub query: Option<String>,
369 pub working_directory: String,
370 pub urgency: String,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct PostContextPerceptionEvent {
376 pub session_id: String,
377 pub intent: String,
378 pub target_type: String,
379 pub success: bool,
380 pub facts_retrieved: usize,
381 pub files_retrieved: usize,
382 #[serde(skip_serializing_if = "Option::is_none")]
383 pub error: Option<String>,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct OnSuccessEvent {
389 pub session_id: String,
391 pub action_type: String,
393 pub action_summary: String,
395 pub duration_ms: u64,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct PreMemoryRecallEvent {
402 pub session_id: String,
403 pub query: String,
405 pub memory_type: String,
407 pub max_results: usize,
409 pub working_directory: String,
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct PostMemoryRecallEvent {
416 pub session_id: String,
417 pub query: String,
418 pub memory_type: String,
419 pub facts_retrieved: usize,
420 pub success: bool,
421 #[serde(skip_serializing_if = "Option::is_none")]
422 pub error: Option<String>,
423}
424
425#[derive(Debug, Clone, Serialize, Deserialize)]
427#[serde(rename_all = "snake_case")]
428pub enum PlanningStrategy {
429 None,
430 StepByStep,
431 TreeOfThoughts,
432 GraphPlanning,
433 Custom(String),
434}
435
436#[derive(Debug, Clone, Serialize, Deserialize)]
438pub struct PrePlanningEvent {
439 pub session_id: String,
440 pub task_description: String,
442 pub available_strategies: Vec<PlanningStrategy>,
444 #[serde(skip_serializing_if = "Option::is_none")]
446 pub constraints: Option<serde_json::Value>,
447}
448
449#[derive(Debug, Clone, Serialize, Deserialize)]
451pub struct PostPlanningEvent {
452 pub session_id: String,
453 pub task_description: String,
454 pub strategy_used: PlanningStrategy,
455 pub subtasks: Vec<String>,
457 pub success: bool,
458 #[serde(skip_serializing_if = "Option::is_none")]
459 pub error: Option<String>,
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize)]
464#[serde(rename_all = "snake_case")]
465pub enum ReasoningType {
466 ChainOfThought,
467 TreeOfThoughts,
468 ReAct,
469 Reflexion,
470 Other(String),
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct PreReasoningEvent {
476 pub session_id: String,
477 pub reasoning_type: ReasoningType,
479 pub problem_statement: String,
481 #[serde(skip_serializing_if = "Option::is_none")]
483 pub hints: Option<Vec<String>>,
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct PostReasoningEvent {
489 pub session_id: String,
490 pub reasoning_type: ReasoningType,
491 pub conclusion: String,
492 pub steps_count: usize,
493 pub success: bool,
494 #[serde(skip_serializing_if = "Option::is_none")]
495 pub error: Option<String>,
496}
497
498#[derive(Debug, Clone, Serialize, Deserialize)]
500#[serde(rename_all = "snake_case")]
501pub enum RateLimitType {
502 LlmTokenLimit,
503 LlmRequestLimit,
504 ApiRequestLimit,
505 ToolExecutionLimit,
506 Custom(String),
507}
508
509#[derive(Debug, Clone, Serialize, Deserialize)]
511pub struct OnRateLimitEvent {
512 pub session_id: String,
513 pub limit_type: RateLimitType,
515 pub retry_after_ms: u64,
517 pub current_usage: String,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523#[serde(rename_all = "snake_case")]
524pub enum ConfirmationType {
525 SafetyConfirm,
526 UserConfirm,
527 CostConfirm,
528 Custom(String),
529}
530
531#[derive(Debug, Clone, Serialize, Deserialize)]
533pub struct OnConfirmationEvent {
534 pub session_id: String,
535 pub confirmation_type: ConfirmationType,
537 pub message: String,
539 #[serde(skip_serializing_if = "Option::is_none")]
541 pub options: Option<Vec<String>>,
542}
543
544#[derive(Debug, Clone, Serialize, Deserialize)]
546pub struct IntentDetectionEvent {
547 pub session_id: String,
548 pub prompt: String,
549 pub workspace: String,
550 #[serde(skip_serializing_if = "Option::is_none")]
552 pub language_hint: Option<String>,
553}
554
555#[derive(Debug, Clone, Serialize, Deserialize)]
558pub struct PreRunControlEvent {
559 pub session_id: String,
560 pub run_id: String,
561 pub request_id: String,
562 pub operation: crate::run_control::RunControlOperation,
563 pub command: crate::run_control::RunControlCommand,
564 #[serde(skip_serializing_if = "Option::is_none")]
565 pub expected_turn_id: Option<String>,
566 #[serde(skip_serializing_if = "Option::is_none")]
567 pub expected_turn_revision: Option<u64>,
568 #[serde(skip_serializing_if = "Option::is_none")]
569 pub deadline_ms: Option<u64>,
570}
571
572#[derive(Debug, Clone, Serialize, Deserialize)]
575pub struct PostRunControlEvent {
576 pub session_id: String,
577 pub run_id: String,
578 pub request_id: String,
579 pub operation: crate::run_control::RunControlOperation,
580 pub state: crate::run_control::RunControlReceiptState,
581 pub sequence: u64,
582 #[serde(skip_serializing_if = "Option::is_none")]
583 pub turn_id: Option<String>,
584 pub turn_revision: u64,
585 pub accepted_at_ms: u64,
586 #[serde(skip_serializing_if = "Option::is_none")]
587 pub applied_at_ms: Option<u64>,
588 #[serde(skip_serializing_if = "Option::is_none")]
589 pub error: Option<crate::run_control::RunControlErrorInfo>,
590}
591
592#[derive(Debug, Clone, Serialize, Deserialize)]
594#[serde(tag = "event_type", content = "payload")]
595pub enum HookEvent {
596 #[serde(rename = "pre_tool_use")]
597 PreToolUse(PreToolUseEvent),
598 #[serde(rename = "post_tool_use")]
599 PostToolUse(PostToolUseEvent),
600 #[serde(rename = "permission_request")]
601 PermissionRequest(PermissionRequestEvent),
602 #[serde(rename = "pre_compact")]
603 PreCompact(PreCompactEvent),
604 #[serde(rename = "post_compact")]
605 PostCompact(PostCompactEvent),
606 #[serde(rename = "generate_start")]
607 GenerateStart(GenerateStartEvent),
608 #[serde(rename = "generate_end")]
609 GenerateEnd(GenerateEndEvent),
610 #[serde(rename = "session_start")]
611 SessionStart(SessionStartEvent),
612 #[serde(rename = "session_end")]
613 SessionEnd(SessionEndEvent),
614 #[serde(rename = "skill_load")]
615 SkillLoad(SkillLoadEvent),
616 #[serde(rename = "skill_unload")]
617 SkillUnload(SkillUnloadEvent),
618 #[serde(rename = "pre_prompt")]
619 PrePrompt(PrePromptEvent),
620 #[serde(rename = "post_response")]
621 PostResponse(PostResponseEvent),
622 #[serde(rename = "on_error")]
623 OnError(OnErrorEvent),
624 #[serde(rename = "pre_context_perception")]
626 PreContextPerception(PreContextPerceptionEvent),
627 #[serde(rename = "post_context_perception")]
628 PostContextPerception(PostContextPerceptionEvent),
629 #[serde(rename = "on_success")]
630 OnSuccess(OnSuccessEvent),
631 #[serde(rename = "pre_memory_recall")]
632 PreMemoryRecall(PreMemoryRecallEvent),
633 #[serde(rename = "post_memory_recall")]
634 PostMemoryRecall(PostMemoryRecallEvent),
635 #[serde(rename = "pre_planning")]
636 PrePlanning(PrePlanningEvent),
637 #[serde(rename = "post_planning")]
638 PostPlanning(PostPlanningEvent),
639 #[serde(rename = "pre_reasoning")]
640 PreReasoning(PreReasoningEvent),
641 #[serde(rename = "post_reasoning")]
642 PostReasoning(PostReasoningEvent),
643 #[serde(rename = "on_rate_limit")]
644 OnRateLimit(OnRateLimitEvent),
645 #[serde(rename = "on_confirmation")]
646 OnConfirmation(OnConfirmationEvent),
647 #[serde(rename = "intent_detection")]
648 IntentDetection(IntentDetectionEvent),
649 #[serde(rename = "pre_run_control")]
650 PreRunControl(PreRunControlEvent),
651 #[serde(rename = "post_run_control")]
652 PostRunControl(PostRunControlEvent),
653}
654
655impl HookEvent {
656 pub fn event_type(&self) -> HookEventType {
658 match self {
659 HookEvent::PreToolUse(_) => HookEventType::PreToolUse,
660 HookEvent::PostToolUse(_) => HookEventType::PostToolUse,
661 HookEvent::PermissionRequest(_) => HookEventType::PermissionRequest,
662 HookEvent::PreCompact(_) => HookEventType::PreCompact,
663 HookEvent::PostCompact(_) => HookEventType::PostCompact,
664 HookEvent::GenerateStart(_) => HookEventType::GenerateStart,
665 HookEvent::GenerateEnd(_) => HookEventType::GenerateEnd,
666 HookEvent::SessionStart(_) => HookEventType::SessionStart,
667 HookEvent::SessionEnd(_) => HookEventType::SessionEnd,
668 HookEvent::SkillLoad(_) => HookEventType::SkillLoad,
669 HookEvent::SkillUnload(_) => HookEventType::SkillUnload,
670 HookEvent::PrePrompt(_) => HookEventType::PrePrompt,
671 HookEvent::PostResponse(_) => HookEventType::PostResponse,
672 HookEvent::OnError(_) => HookEventType::OnError,
673 HookEvent::PreContextPerception(_) => HookEventType::PreContextPerception,
675 HookEvent::PostContextPerception(_) => HookEventType::PostContextPerception,
676 HookEvent::OnSuccess(_) => HookEventType::OnSuccess,
677 HookEvent::PreMemoryRecall(_) => HookEventType::PreMemoryRecall,
678 HookEvent::PostMemoryRecall(_) => HookEventType::PostMemoryRecall,
679 HookEvent::PrePlanning(_) => HookEventType::PrePlanning,
680 HookEvent::PostPlanning(_) => HookEventType::PostPlanning,
681 HookEvent::PreReasoning(_) => HookEventType::PreReasoning,
682 HookEvent::PostReasoning(_) => HookEventType::PostReasoning,
683 HookEvent::OnRateLimit(_) => HookEventType::OnRateLimit,
684 HookEvent::OnConfirmation(_) => HookEventType::OnConfirmation,
685 HookEvent::IntentDetection(_) => HookEventType::IntentDetection,
686 HookEvent::PreRunControl(_) => HookEventType::PreRunControl,
687 HookEvent::PostRunControl(_) => HookEventType::PostRunControl,
688 }
689 }
690
691 pub fn session_id(&self) -> &str {
693 match self {
694 HookEvent::PreToolUse(e) => &e.session_id,
695 HookEvent::PostToolUse(e) => &e.session_id,
696 HookEvent::PermissionRequest(e) => &e.session_id,
697 HookEvent::PreCompact(e) => &e.session_id,
698 HookEvent::PostCompact(e) => &e.session_id,
699 HookEvent::GenerateStart(e) => &e.session_id,
700 HookEvent::GenerateEnd(e) => &e.session_id,
701 HookEvent::SessionStart(e) => &e.session_id,
702 HookEvent::SessionEnd(e) => &e.session_id,
703 HookEvent::PrePrompt(e) => &e.session_id,
704 HookEvent::PostResponse(e) => &e.session_id,
705 HookEvent::OnError(e) => &e.session_id,
706 HookEvent::PreContextPerception(e) => &e.session_id,
708 HookEvent::PostContextPerception(e) => &e.session_id,
709 HookEvent::OnSuccess(e) => &e.session_id,
710 HookEvent::PreMemoryRecall(e) => &e.session_id,
711 HookEvent::PostMemoryRecall(e) => &e.session_id,
712 HookEvent::PrePlanning(e) => &e.session_id,
713 HookEvent::PostPlanning(e) => &e.session_id,
714 HookEvent::PreReasoning(e) => &e.session_id,
715 HookEvent::PostReasoning(e) => &e.session_id,
716 HookEvent::OnRateLimit(e) => &e.session_id,
717 HookEvent::OnConfirmation(e) => &e.session_id,
718 HookEvent::IntentDetection(e) => &e.session_id,
719 HookEvent::PreRunControl(e) => &e.session_id,
720 HookEvent::PostRunControl(e) => &e.session_id,
721 HookEvent::SkillLoad(_) => "",
723 HookEvent::SkillUnload(_) => "",
724 }
725 }
726
727 pub fn tool_name(&self) -> Option<&str> {
729 match self {
730 HookEvent::PreToolUse(e) => Some(&e.tool),
731 HookEvent::PostToolUse(e) => Some(&e.tool),
732 _ => None,
733 }
734 }
735
736 pub fn tool_args(&self) -> Option<&serde_json::Value> {
738 match self {
739 HookEvent::PreToolUse(e) => Some(&e.args),
740 HookEvent::PostToolUse(e) => Some(&e.args),
741 _ => None,
742 }
743 }
744
745 pub fn skill_name(&self) -> Option<&str> {
747 match self {
748 HookEvent::SkillLoad(e) => Some(&e.skill_name),
749 HookEvent::SkillUnload(e) => Some(&e.skill_name),
750 _ => None,
751 }
752 }
753}
754
755#[cfg(test)]
756mod tests {
757 use super::*;
758
759 #[test]
760 fn test_hook_event_type_display() {
761 assert_eq!(HookEventType::PreToolUse.to_string(), "pre_tool_use");
762 assert_eq!(HookEventType::PostToolUse.to_string(), "post_tool_use");
763 assert_eq!(HookEventType::GenerateStart.to_string(), "generate_start");
764 assert_eq!(HookEventType::GenerateEnd.to_string(), "generate_end");
765 assert_eq!(HookEventType::SessionStart.to_string(), "session_start");
766 assert_eq!(HookEventType::SessionEnd.to_string(), "session_end");
767 assert_eq!(HookEventType::SkillLoad.to_string(), "skill_load");
768 assert_eq!(HookEventType::SkillUnload.to_string(), "skill_unload");
769 }
770
771 #[test]
772 fn test_pre_tool_use_event() {
773 let event = PreToolUseEvent {
774 session_id: "session-1".to_string(),
775 tool: "Bash".to_string(),
776 args: serde_json::json!({"command": "echo hello"}),
777 working_directory: "/workspace".to_string(),
778 recent_tools: vec!["Read".to_string()],
779 };
780
781 assert_eq!(event.session_id, "session-1");
782 assert_eq!(event.tool, "Bash");
783 }
784
785 #[test]
786 fn test_post_tool_use_event() {
787 let event = PostToolUseEvent {
788 session_id: "session-1".to_string(),
789 tool: "Bash".to_string(),
790 args: serde_json::json!({"command": "echo hello"}),
791 result: ToolResultData {
792 success: true,
793 output: "hello\n".to_string(),
794 exit_code: Some(0),
795 duration_ms: 50,
796 },
797 };
798
799 assert!(event.result.success);
800 assert_eq!(event.result.exit_code, Some(0));
801 }
802
803 #[test]
804 fn test_hook_event_type() {
805 let pre_tool = HookEvent::PreToolUse(PreToolUseEvent {
806 session_id: "s1".to_string(),
807 tool: "Bash".to_string(),
808 args: serde_json::json!({}),
809 working_directory: "/".to_string(),
810 recent_tools: vec![],
811 });
812
813 assert_eq!(pre_tool.event_type(), HookEventType::PreToolUse);
814 assert_eq!(pre_tool.session_id(), "s1");
815 assert_eq!(pre_tool.tool_name(), Some("Bash"));
816 }
817
818 #[test]
819 fn test_hook_event_serialization() {
820 let event = HookEvent::PreToolUse(PreToolUseEvent {
821 session_id: "s1".to_string(),
822 tool: "Bash".to_string(),
823 args: serde_json::json!({"command": "ls"}),
824 working_directory: "/workspace".to_string(),
825 recent_tools: vec![],
826 });
827
828 let json = serde_json::to_string(&event).unwrap();
829 assert!(json.contains("pre_tool_use"));
830 assert!(json.contains("Bash"));
831
832 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
834 assert_eq!(parsed.event_type(), HookEventType::PreToolUse);
835 }
836
837 #[test]
838 fn test_generate_events() {
839 let start = GenerateStartEvent {
840 session_id: "s1".to_string(),
841 prompt: "Hello".to_string(),
842 system_prompt: Some("You are helpful".to_string()),
843 model_provider: "anthropic".to_string(),
844 model_name: "claude-3".to_string(),
845 available_tools: vec!["Bash".to_string(), "Read".to_string()],
846 };
847
848 let end = GenerateEndEvent {
849 session_id: "s1".to_string(),
850 prompt: "Hello".to_string(),
851 response_text: "Hi there!".to_string(),
852 tool_calls: vec![],
853 usage: TokenUsageInfo {
854 prompt_tokens: 10,
855 completion_tokens: 5,
856 total_tokens: 15,
857 },
858 duration_ms: 500,
859 };
860
861 assert_eq!(start.prompt, "Hello");
862 assert_eq!(end.response_text, "Hi there!");
863 assert_eq!(end.usage.total_tokens, 15);
864 }
865
866 #[test]
867 fn test_session_events() {
868 let start = SessionStartEvent {
869 session_id: "s1".to_string(),
870 system_prompt: Some("System".to_string()),
871 model_provider: "anthropic".to_string(),
872 model_name: "claude-3".to_string(),
873 };
874
875 let end = SessionEndEvent {
876 session_id: "s1".to_string(),
877 total_tokens: 1000,
878 total_tool_calls: 5,
879 duration_ms: 60000,
880 };
881
882 let start_event = HookEvent::SessionStart(start);
883 let end_event = HookEvent::SessionEnd(end);
884
885 assert_eq!(start_event.event_type(), HookEventType::SessionStart);
886 assert_eq!(end_event.event_type(), HookEventType::SessionEnd);
887 assert!(start_event.tool_name().is_none());
888 }
889
890 #[test]
891 fn test_skill_load_event() {
892 let event = SkillLoadEvent {
893 skill_name: "test-skill".to_string(),
894 tool_names: vec!["tool1".to_string(), "tool2".to_string()],
895 version: Some("1.0.0".to_string()),
896 description: Some("A test skill".to_string()),
897 loaded_at: 1234567890,
898 };
899
900 assert_eq!(event.skill_name, "test-skill");
901 assert_eq!(event.tool_names.len(), 2);
902 assert_eq!(event.version, Some("1.0.0".to_string()));
903 assert_eq!(event.loaded_at, 1234567890);
904 }
905
906 #[test]
907 fn test_skill_unload_event() {
908 let event = SkillUnloadEvent {
909 skill_name: "test-skill".to_string(),
910 tool_names: vec!["tool1".to_string(), "tool2".to_string()],
911 duration_ms: 60000,
912 };
913
914 assert_eq!(event.skill_name, "test-skill");
915 assert_eq!(event.tool_names.len(), 2);
916 assert_eq!(event.duration_ms, 60000);
917 }
918
919 #[test]
920 fn test_hook_event_skill_name() {
921 let load_event = HookEvent::SkillLoad(SkillLoadEvent {
922 skill_name: "my-skill".to_string(),
923 tool_names: vec!["tool1".to_string()],
924 version: None,
925 description: None,
926 loaded_at: 0,
927 });
928
929 let unload_event = HookEvent::SkillUnload(SkillUnloadEvent {
930 skill_name: "my-skill".to_string(),
931 tool_names: vec!["tool1".to_string()],
932 duration_ms: 1000,
933 });
934
935 assert_eq!(load_event.event_type(), HookEventType::SkillLoad);
936 assert_eq!(load_event.skill_name(), Some("my-skill"));
937 assert_eq!(load_event.session_id(), ""); assert_eq!(unload_event.event_type(), HookEventType::SkillUnload);
940 assert_eq!(unload_event.skill_name(), Some("my-skill"));
941 assert_eq!(unload_event.session_id(), ""); let pre_tool = HookEvent::PreToolUse(PreToolUseEvent {
945 session_id: "s1".to_string(),
946 tool: "Bash".to_string(),
947 args: serde_json::json!({}),
948 working_directory: "/".to_string(),
949 recent_tools: vec![],
950 });
951 assert!(pre_tool.skill_name().is_none());
952 }
953
954 #[test]
955 fn test_skill_event_serialization() {
956 let event = HookEvent::SkillLoad(SkillLoadEvent {
957 skill_name: "test-skill".to_string(),
958 tool_names: vec!["tool1".to_string()],
959 version: Some("1.0.0".to_string()),
960 description: None,
961 loaded_at: 1234567890,
962 });
963
964 let json = serde_json::to_string(&event).unwrap();
965 assert!(json.contains("skill_load"));
966 assert!(json.contains("test-skill"));
967 assert!(json.contains("1.0.0"));
968
969 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
970 assert_eq!(parsed.event_type(), HookEventType::SkillLoad);
971 assert_eq!(parsed.skill_name(), Some("test-skill"));
972 }
973
974 #[test]
975 fn test_hook_event_type_display_new_variants() {
976 assert_eq!(HookEventType::PrePrompt.to_string(), "pre_prompt");
977 assert_eq!(HookEventType::PostResponse.to_string(), "post_response");
978 assert_eq!(HookEventType::OnError.to_string(), "on_error");
979 }
980
981 #[test]
982 fn test_pre_prompt_event() {
983 let event = PrePromptEvent {
984 session_id: "s1".to_string(),
985 prompt: "Fix the bug".to_string(),
986 system_prompt: Some("You are helpful".to_string()),
987 message_count: 5,
988 };
989
990 assert_eq!(event.session_id, "s1");
991 assert_eq!(event.prompt, "Fix the bug");
992 assert_eq!(event.message_count, 5);
993
994 let hook_event = HookEvent::PrePrompt(event);
995 assert_eq!(hook_event.event_type(), HookEventType::PrePrompt);
996 assert_eq!(hook_event.session_id(), "s1");
997 assert!(hook_event.tool_name().is_none());
998 assert!(hook_event.skill_name().is_none());
999 }
1000
1001 #[test]
1002 fn test_post_response_event() {
1003 let event = PostResponseEvent {
1004 session_id: "s1".to_string(),
1005 response_text: "Done!".to_string(),
1006 tool_calls_count: 3,
1007 usage: TokenUsageInfo {
1008 prompt_tokens: 100,
1009 completion_tokens: 50,
1010 total_tokens: 150,
1011 },
1012 duration_ms: 2000,
1013 };
1014
1015 assert_eq!(event.response_text, "Done!");
1016 assert_eq!(event.tool_calls_count, 3);
1017 assert_eq!(event.usage.total_tokens, 150);
1018
1019 let hook_event = HookEvent::PostResponse(event);
1020 assert_eq!(hook_event.event_type(), HookEventType::PostResponse);
1021 assert_eq!(hook_event.session_id(), "s1");
1022 }
1023
1024 #[test]
1025 fn test_on_error_event() {
1026 let event = OnErrorEvent {
1027 session_id: "s1".to_string(),
1028 error_type: ErrorType::ToolFailure,
1029 error_message: "Command failed with exit code 1".to_string(),
1030 context: serde_json::json!({"tool": "Bash", "command": "false"}),
1031 };
1032
1033 assert_eq!(event.error_type.to_string(), "tool_failure");
1034 assert_eq!(event.error_message, "Command failed with exit code 1");
1035
1036 let hook_event = HookEvent::OnError(event);
1037 assert_eq!(hook_event.event_type(), HookEventType::OnError);
1038 assert_eq!(hook_event.session_id(), "s1");
1039 }
1040
1041 #[test]
1042 fn test_error_type_display() {
1043 assert_eq!(ErrorType::ToolFailure.to_string(), "tool_failure");
1044 assert_eq!(ErrorType::LlmFailure.to_string(), "llm_failure");
1045 assert_eq!(ErrorType::PermissionDenied.to_string(), "permission_denied");
1046 assert_eq!(ErrorType::Timeout.to_string(), "timeout");
1047 assert_eq!(ErrorType::Other.to_string(), "other");
1048 }
1049
1050 #[test]
1051 fn test_new_event_serialization() {
1052 let event = HookEvent::PrePrompt(PrePromptEvent {
1054 session_id: "s1".to_string(),
1055 prompt: "Hello".to_string(),
1056 system_prompt: None,
1057 message_count: 0,
1058 });
1059 let json = serde_json::to_string(&event).unwrap();
1060 assert!(json.contains("pre_prompt"));
1061 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
1062 assert_eq!(parsed.event_type(), HookEventType::PrePrompt);
1063
1064 let event = HookEvent::PostResponse(PostResponseEvent {
1066 session_id: "s1".to_string(),
1067 response_text: "Hi".to_string(),
1068 tool_calls_count: 0,
1069 usage: TokenUsageInfo {
1070 prompt_tokens: 10,
1071 completion_tokens: 5,
1072 total_tokens: 15,
1073 },
1074 duration_ms: 100,
1075 });
1076 let json = serde_json::to_string(&event).unwrap();
1077 assert!(json.contains("post_response"));
1078 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
1079 assert_eq!(parsed.event_type(), HookEventType::PostResponse);
1080
1081 let event = HookEvent::OnError(OnErrorEvent {
1083 session_id: "s1".to_string(),
1084 error_type: ErrorType::LlmFailure,
1085 error_message: "API timeout".to_string(),
1086 context: serde_json::json!({}),
1087 });
1088 let json = serde_json::to_string(&event).unwrap();
1089 assert!(json.contains("on_error"));
1090 let parsed: HookEvent = serde_json::from_str(&json).unwrap();
1091 assert_eq!(parsed.event_type(), HookEventType::OnError);
1092 }
1093}