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 ErrorType, GenerateEndEvent, GenerateStartEvent, HookEvent, HookEventType, OnErrorEvent,
44 PostResponseEvent, PostToolUseEvent, PrePromptEvent, PreToolUseEvent, SessionEndEvent,
45 SessionStartEvent, SkillLoadEvent, SkillUnloadEvent, TokenUsageInfo, ToolCallInfo,
46 ToolResultData,
47};
48pub use matcher::HookMatcher;
49
50#[derive(Debug, Clone, PartialEq)]
52pub enum HookAction {
53 Continue,
55 Block,
57 Retry,
59 Skip,
61}
62
63#[derive(Debug, Clone)]
65pub struct HookResponse {
66 pub hook_id: String,
68 pub action: HookAction,
70 pub reason: Option<String>,
72 pub modified: Option<serde_json::Value>,
74 pub retry_delay_ms: Option<u64>,
76}
77
78impl HookResponse {
79 pub fn continue_() -> Self {
81 Self {
82 hook_id: String::new(),
83 action: HookAction::Continue,
84 reason: None,
85 modified: None,
86 retry_delay_ms: None,
87 }
88 }
89
90 pub fn continue_with(modified: serde_json::Value) -> Self {
92 Self {
93 hook_id: String::new(),
94 action: HookAction::Continue,
95 reason: None,
96 modified: Some(modified),
97 retry_delay_ms: None,
98 }
99 }
100
101 pub fn block(reason: impl Into<String>) -> Self {
103 Self {
104 hook_id: String::new(),
105 action: HookAction::Block,
106 reason: Some(reason.into()),
107 modified: None,
108 retry_delay_ms: None,
109 }
110 }
111
112 pub fn retry(delay_ms: u64) -> Self {
114 Self {
115 hook_id: String::new(),
116 action: HookAction::Retry,
117 reason: None,
118 modified: None,
119 retry_delay_ms: Some(delay_ms),
120 }
121 }
122
123 pub fn skip() -> Self {
125 Self {
126 hook_id: String::new(),
127 action: HookAction::Skip,
128 reason: None,
129 modified: None,
130 retry_delay_ms: None,
131 }
132 }
133
134 pub fn with_hook_id(mut self, id: impl Into<String>) -> Self {
136 self.hook_id = id.into();
137 self
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_hook_response_continue() {
147 let response = HookResponse::continue_();
148 assert_eq!(response.action, HookAction::Continue);
149 assert!(response.reason.is_none());
150 assert!(response.modified.is_none());
151 }
152
153 #[test]
154 fn test_hook_response_continue_with_modified() {
155 let modified = serde_json::json!({"timeout": 5000});
156 let response = HookResponse::continue_with(modified.clone());
157 assert_eq!(response.action, HookAction::Continue);
158 assert_eq!(response.modified, Some(modified));
159 }
160
161 #[test]
162 fn test_hook_response_block() {
163 let response = HookResponse::block("Dangerous command");
164 assert_eq!(response.action, HookAction::Block);
165 assert_eq!(response.reason, Some("Dangerous command".to_string()));
166 }
167
168 #[test]
169 fn test_hook_response_retry() {
170 let response = HookResponse::retry(1000);
171 assert_eq!(response.action, HookAction::Retry);
172 assert_eq!(response.retry_delay_ms, Some(1000));
173 }
174
175 #[test]
176 fn test_hook_response_skip() {
177 let response = HookResponse::skip();
178 assert_eq!(response.action, HookAction::Skip);
179 }
180
181 #[test]
182 fn test_hook_response_with_hook_id() {
183 let response = HookResponse::continue_().with_hook_id("hook-123");
184 assert_eq!(response.hook_id, "hook-123");
185 }
186}