a3s_code_core/hooks/
mod.rs1mod engine;
38mod events;
39mod matcher;
40
41pub use engine::{Hook, HookConfig, HookEngine, HookExecutor, HookHandler, HookResult};
42pub use events::{
43 GenerateEndEvent, GenerateStartEvent, HookEvent, HookEventType, PostToolUseEvent,
44 PreToolUseEvent, SessionEndEvent, SessionStartEvent, SkillLoadEvent, SkillUnloadEvent,
45 TokenUsageInfo, ToolCallInfo, ToolResultData,
46};
47pub use matcher::HookMatcher;
48
49#[derive(Debug, Clone, PartialEq)]
51pub enum HookAction {
52 Continue,
54 Block,
56 Retry,
58 Skip,
60}
61
62#[derive(Debug, Clone)]
64pub struct HookResponse {
65 pub hook_id: String,
67 pub action: HookAction,
69 pub reason: Option<String>,
71 pub modified: Option<serde_json::Value>,
73 pub retry_delay_ms: Option<u64>,
75}
76
77impl HookResponse {
78 pub fn continue_() -> Self {
80 Self {
81 hook_id: String::new(),
82 action: HookAction::Continue,
83 reason: None,
84 modified: None,
85 retry_delay_ms: None,
86 }
87 }
88
89 pub fn continue_with(modified: serde_json::Value) -> Self {
91 Self {
92 hook_id: String::new(),
93 action: HookAction::Continue,
94 reason: None,
95 modified: Some(modified),
96 retry_delay_ms: None,
97 }
98 }
99
100 pub fn block(reason: impl Into<String>) -> Self {
102 Self {
103 hook_id: String::new(),
104 action: HookAction::Block,
105 reason: Some(reason.into()),
106 modified: None,
107 retry_delay_ms: None,
108 }
109 }
110
111 pub fn retry(delay_ms: u64) -> Self {
113 Self {
114 hook_id: String::new(),
115 action: HookAction::Retry,
116 reason: None,
117 modified: None,
118 retry_delay_ms: Some(delay_ms),
119 }
120 }
121
122 pub fn skip() -> Self {
124 Self {
125 hook_id: String::new(),
126 action: HookAction::Skip,
127 reason: None,
128 modified: None,
129 retry_delay_ms: None,
130 }
131 }
132
133 pub fn with_hook_id(mut self, id: impl Into<String>) -> Self {
135 self.hook_id = id.into();
136 self
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_hook_response_continue() {
146 let response = HookResponse::continue_();
147 assert_eq!(response.action, HookAction::Continue);
148 assert!(response.reason.is_none());
149 assert!(response.modified.is_none());
150 }
151
152 #[test]
153 fn test_hook_response_continue_with_modified() {
154 let modified = serde_json::json!({"timeout": 5000});
155 let response = HookResponse::continue_with(modified.clone());
156 assert_eq!(response.action, HookAction::Continue);
157 assert_eq!(response.modified, Some(modified));
158 }
159
160 #[test]
161 fn test_hook_response_block() {
162 let response = HookResponse::block("Dangerous command");
163 assert_eq!(response.action, HookAction::Block);
164 assert_eq!(response.reason, Some("Dangerous command".to_string()));
165 }
166
167 #[test]
168 fn test_hook_response_retry() {
169 let response = HookResponse::retry(1000);
170 assert_eq!(response.action, HookAction::Retry);
171 assert_eq!(response.retry_delay_ms, Some(1000));
172 }
173
174 #[test]
175 fn test_hook_response_skip() {
176 let response = HookResponse::skip();
177 assert_eq!(response.action, HookAction::Skip);
178 }
179
180 #[test]
181 fn test_hook_response_with_hook_id() {
182 let response = HookResponse::continue_().with_hook_id("hook-123");
183 assert_eq!(response.hook_id, "hook-123");
184 }
185}