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 PlanningStrategy, PostContextPerceptionEvent, PostMemoryRecallEvent, PostPlanningEvent,
50 PostReasoningEvent, PostResponseEvent, PostToolUseEvent, PreContextPerceptionEvent,
51 PreMemoryRecallEvent, PrePlanningEvent, PrePromptEvent, PreReasoningEvent, PreToolUseEvent,
52 RateLimitType, ReasoningType, SessionEndEvent, SessionStartEvent, SkillLoadEvent,
53 SkillUnloadEvent, TokenUsageInfo, ToolCallInfo, ToolResultData,
54};
55pub use matcher::HookMatcher;
56
57#[derive(Debug, Clone, PartialEq)]
59pub enum HookAction {
60 Continue,
62 Block,
64 Retry,
66 Skip,
68}
69
70#[derive(Debug, Clone)]
72pub struct HookResponse {
73 pub hook_id: String,
75 pub action: HookAction,
77 pub reason: Option<String>,
79 pub modified: Option<serde_json::Value>,
81 pub retry_delay_ms: Option<u64>,
83}
84
85impl HookResponse {
86 pub fn continue_() -> Self {
88 Self {
89 hook_id: String::new(),
90 action: HookAction::Continue,
91 reason: None,
92 modified: None,
93 retry_delay_ms: None,
94 }
95 }
96
97 pub fn continue_with(modified: serde_json::Value) -> Self {
99 Self {
100 hook_id: String::new(),
101 action: HookAction::Continue,
102 reason: None,
103 modified: Some(modified),
104 retry_delay_ms: None,
105 }
106 }
107
108 pub fn block(reason: impl Into<String>) -> Self {
110 Self {
111 hook_id: String::new(),
112 action: HookAction::Block,
113 reason: Some(reason.into()),
114 modified: None,
115 retry_delay_ms: None,
116 }
117 }
118
119 pub fn retry(delay_ms: u64) -> Self {
121 Self {
122 hook_id: String::new(),
123 action: HookAction::Retry,
124 reason: None,
125 modified: None,
126 retry_delay_ms: Some(delay_ms),
127 }
128 }
129
130 pub fn retry_with_reason(reason: impl Into<String>, delay_ms: u64) -> Self {
132 Self {
133 hook_id: String::new(),
134 action: HookAction::Retry,
135 reason: Some(reason.into()),
136 modified: None,
137 retry_delay_ms: Some(delay_ms),
138 }
139 }
140
141 pub fn skip() -> Self {
143 Self {
144 hook_id: String::new(),
145 action: HookAction::Skip,
146 reason: None,
147 modified: None,
148 retry_delay_ms: None,
149 }
150 }
151
152 pub fn with_hook_id(mut self, id: impl Into<String>) -> Self {
154 self.hook_id = id.into();
155 self
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn test_hook_response_continue() {
165 let response = HookResponse::continue_();
166 assert_eq!(response.action, HookAction::Continue);
167 assert!(response.reason.is_none());
168 assert!(response.modified.is_none());
169 }
170
171 #[test]
172 fn test_hook_response_continue_with_modified() {
173 let modified = serde_json::json!({"timeout": 5000});
174 let response = HookResponse::continue_with(modified.clone());
175 assert_eq!(response.action, HookAction::Continue);
176 assert_eq!(response.modified, Some(modified));
177 }
178
179 #[test]
180 fn test_hook_response_block() {
181 let response = HookResponse::block("Dangerous command");
182 assert_eq!(response.action, HookAction::Block);
183 assert_eq!(response.reason, Some("Dangerous command".to_string()));
184 }
185
186 #[test]
187 fn test_hook_response_retry() {
188 let response = HookResponse::retry(1000);
189 assert_eq!(response.action, HookAction::Retry);
190 assert_eq!(response.retry_delay_ms, Some(1000));
191 }
192
193 #[test]
194 fn test_hook_response_retry_with_reason() {
195 let response = HookResponse::retry_with_reason("temporary outage", 750);
196 assert_eq!(response.action, HookAction::Retry);
197 assert_eq!(response.reason.as_deref(), Some("temporary outage"));
198 assert_eq!(response.retry_delay_ms, Some(750));
199 }
200
201 #[test]
202 fn test_hook_response_skip() {
203 let response = HookResponse::skip();
204 assert_eq!(response.action, HookAction::Skip);
205 }
206
207 #[test]
208 fn test_hook_response_with_hook_id() {
209 let response = HookResponse::continue_().with_hook_id("hook-123");
210 assert_eq!(response.hook_id, "hook-123");
211 }
212}