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