1mod engine;
40mod events;
41mod matcher;
42
43pub use engine::{
44 Hook, HookConfig, HookEngine, HookExecutor, HookHandler, HookOutcome, HookResult,
45};
46pub use events::{
47 ConfirmationType, ErrorType, GenerateEndEvent, GenerateStartEvent, HookEvent, HookEventType,
48 IntentDetectionEvent, OnConfirmationEvent, OnErrorEvent, OnRateLimitEvent, OnSuccessEvent,
49 PermissionRequestEvent, PlanningStrategy, PostCompactEvent, PostContextPerceptionEvent,
50 PostMemoryRecallEvent, PostPlanningEvent, PostReasoningEvent, PostResponseEvent,
51 PostToolUseEvent, PreCompactEvent, PreContextPerceptionEvent, PreMemoryRecallEvent,
52 PrePlanningEvent, PrePromptEvent, PreReasoningEvent, PreToolUseEvent, RateLimitType,
53 ReasoningType, SessionEndEvent, SessionStartEvent, SkillLoadEvent, SkillUnloadEvent,
54 TokenUsageInfo, ToolCallInfo, ToolResultData,
55};
56pub use matcher::HookMatcher;
57
58#[derive(Debug, Clone, PartialEq)]
60pub enum HookAction {
61 Continue,
63 Block,
65 Retry,
67 Skip,
69}
70
71#[derive(Debug, Clone)]
73pub struct HookResponse {
74 pub hook_id: String,
76 pub action: HookAction,
78 pub reason: Option<String>,
80 pub modified: Option<serde_json::Value>,
82 pub retry_delay_ms: Option<u64>,
84}
85
86impl HookResponse {
87 pub fn continue_() -> Self {
89 Self {
90 hook_id: String::new(),
91 action: HookAction::Continue,
92 reason: None,
93 modified: None,
94 retry_delay_ms: None,
95 }
96 }
97
98 pub fn continue_with(modified: serde_json::Value) -> Self {
100 Self {
101 hook_id: String::new(),
102 action: HookAction::Continue,
103 reason: None,
104 modified: Some(modified),
105 retry_delay_ms: None,
106 }
107 }
108
109 pub fn block(reason: impl Into<String>) -> Self {
111 Self {
112 hook_id: String::new(),
113 action: HookAction::Block,
114 reason: Some(reason.into()),
115 modified: None,
116 retry_delay_ms: None,
117 }
118 }
119
120 pub fn retry(delay_ms: u64) -> Self {
122 Self {
123 hook_id: String::new(),
124 action: HookAction::Retry,
125 reason: None,
126 modified: None,
127 retry_delay_ms: Some(delay_ms),
128 }
129 }
130
131 pub fn retry_with_reason(reason: impl Into<String>, delay_ms: u64) -> Self {
133 Self {
134 hook_id: String::new(),
135 action: HookAction::Retry,
136 reason: Some(reason.into()),
137 modified: None,
138 retry_delay_ms: Some(delay_ms),
139 }
140 }
141
142 pub fn skip() -> Self {
144 Self {
145 hook_id: String::new(),
146 action: HookAction::Skip,
147 reason: None,
148 modified: None,
149 retry_delay_ms: None,
150 }
151 }
152
153 pub fn with_hook_id(mut self, id: impl Into<String>) -> Self {
155 self.hook_id = id.into();
156 self
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_hook_response_continue() {
166 let response = HookResponse::continue_();
167 assert_eq!(response.action, HookAction::Continue);
168 assert!(response.reason.is_none());
169 assert!(response.modified.is_none());
170 }
171
172 #[test]
173 fn test_hook_response_continue_with_modified() {
174 let modified = serde_json::json!({"timeout": 5000});
175 let response = HookResponse::continue_with(modified.clone());
176 assert_eq!(response.action, HookAction::Continue);
177 assert_eq!(response.modified, Some(modified));
178 }
179
180 #[test]
181 fn test_hook_response_block() {
182 let response = HookResponse::block("Dangerous command");
183 assert_eq!(response.action, HookAction::Block);
184 assert_eq!(response.reason, Some("Dangerous command".to_string()));
185 }
186
187 #[test]
188 fn test_hook_response_retry() {
189 let response = HookResponse::retry(1000);
190 assert_eq!(response.action, HookAction::Retry);
191 assert_eq!(response.retry_delay_ms, Some(1000));
192 }
193
194 #[test]
195 fn test_hook_response_retry_with_reason() {
196 let response = HookResponse::retry_with_reason("temporary outage", 750);
197 assert_eq!(response.action, HookAction::Retry);
198 assert_eq!(response.reason.as_deref(), Some("temporary outage"));
199 assert_eq!(response.retry_delay_ms, Some(750));
200 }
201
202 #[test]
203 fn test_hook_response_skip() {
204 let response = HookResponse::skip();
205 assert_eq!(response.action, HookAction::Skip);
206 }
207
208 #[test]
209 fn test_hook_response_with_hook_id() {
210 let response = HookResponse::continue_().with_hook_id("hook-123");
211 assert_eq!(response.hook_id, "hook-123");
212 }
213}