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