Skip to main content

open_agent/hooks/
events.rs

1/// Event fired **before** a tool is executed, enabling validation, modification, or blocking.
2///
3/// This event provides complete visibility into the tool that's about to be executed,
4/// allowing you to implement security policies, modify inputs, or collect telemetry
5/// before any potentially dangerous or expensive operations occur.
6///
7/// # Use Cases
8///
9/// - **Security gates**: Block dangerous operations (file deletion, network access)
10/// - **Input validation**: Ensure tool inputs meet schema or business rules
11/// - **Parameter injection**: Add authentication tokens, user context, or default values
12/// - **Rate limiting**: Track and limit tool usage per user/session
13/// - **Audit logging**: Record who is calling what tools with what parameters
14///
15/// # Fields
16///
17/// - `tool_name`: The name of the tool about to execute (e.g., "Bash", "Read", "WebFetch")
18/// - `tool_input`: The parameters that will be passed to the tool (as JSON)
19/// - `tool_use_id`: Unique identifier for this specific tool invocation
20/// - `history`: Read-only snapshot of the conversation history up to this point
21///
22/// # Example: Security Gate
23///
24/// ```rust
25/// use open_agent::{PreToolUseEvent, HookDecision};
26/// use serde_json::json;
27///
28/// async fn security_gate(event: PreToolUseEvent) -> Option<HookDecision> {
29///     // Block all Bash commands containing 'rm -rf'
30///     if event.tool_name == "Bash" {
31///         if let Some(command) = event.tool_input.get("command") {
32///             if command.as_str()?.contains("rm -rf") {
33///                 return Some(HookDecision::block(
34///                     "Dangerous command blocked for safety"
35///                 ));
36///             }
37///         }
38///     }
39///     None // Allow other tools
40/// }
41/// ```
42///
43/// # Example: Parameter Injection
44///
45/// ```rust
46/// use open_agent::{PreToolUseEvent, HookDecision};
47/// use serde_json::json;
48///
49/// async fn inject_auth(event: PreToolUseEvent) -> Option<HookDecision> {
50///     // Add authentication header to all API calls
51///     if event.tool_name == "WebFetch" {
52///         let mut modified = event.tool_input.clone();
53///         modified["headers"] = json!({
54///             "Authorization": "Bearer secret-token"
55///         });
56///         return Some(HookDecision::modify_input(
57///             modified,
58///             "Injected auth token"
59///         ));
60///     }
61///     None
62/// }
63/// ```
64#[derive(Debug, Clone)]
65pub struct PreToolUseEvent {
66    /// Name of the tool about to be executed (e.g., "Bash", "Read", "Edit")
67    pub tool_name: String,
68    /// Input parameters for the tool as a JSON value
69    pub tool_input: Value,
70    /// Unique identifier for this tool use (for correlation with PostToolUseEvent)
71    pub tool_use_id: String,
72    /// Structured JSON snapshot of conversation history before this tool call executes.
73    pub history: Vec<Value>,
74}
75
76impl PreToolUseEvent {
77    /// Creates a new PreToolUseEvent.
78    ///
79    /// This constructor is typically called by the agent runtime, not by user code.
80    /// Users receive instances of this struct in their hook handlers.
81    pub fn new(
82        tool_name: String,
83        tool_input: Value,
84        tool_use_id: String,
85        history: Vec<Value>,
86    ) -> Self {
87        Self {
88            tool_name,
89            tool_input,
90            tool_use_id,
91            history,
92        }
93    }
94}
95
96/// Event fired **after** a tool completes execution, enabling audit, filtering, or validation.
97///
98/// This event provides complete visibility into what a tool did, including both the input
99/// parameters and the output result. Use this for auditing, metrics collection, output
100/// filtering, or post-execution validation.
101///
102/// # Use Cases
103///
104/// - **Audit logging**: Record all tool executions with inputs and outputs for compliance
105/// - **Output filtering**: Redact sensitive information from tool results
106/// - **Metrics collection**: Track tool performance, success rates, error patterns
107/// - **Result validation**: Ensure tool outputs meet quality or safety standards
108/// - **Error handling**: Implement custom error recovery or alerting
109///
110/// # Fields
111///
112/// - `tool_name`: The name of the tool that was executed
113/// - `tool_input`: The parameters that were actually used (may have been modified by PreToolUse hooks)
114/// - `tool_use_id`: Unique identifier for this invocation (matches PreToolUseEvent.tool_use_id)
115/// - `tool_result`: The result returned by the tool (contains either success data or error info)
116/// - `history`: Read-only snapshot of conversation history including this tool's execution
117///
118/// # Example: Audit Logging
119///
120/// ```rust
121/// use open_agent::{PostToolUseEvent, HookDecision};
122///
123/// async fn audit_logger(event: PostToolUseEvent) -> Option<HookDecision> {
124///     // Log all tool executions to your audit system
125///     let is_error = event.tool_result.get("error").is_some();
126///
127///     println!(
128///         "[AUDIT] Tool: {}, ID: {}, Status: {}",
129///         event.tool_name,
130///         event.tool_use_id,
131///         if is_error { "ERROR" } else { "SUCCESS" }
132///     );
133///
134///     // Send to external logging service
135///     // log_to_service(&event).await;
136///
137///     None // Don't interfere with execution
138/// }
139/// ```
140///
141/// # Example: Sensitive Data Redaction
142///
143/// ```rust
144/// use open_agent::{PostToolUseEvent, HookDecision};
145/// use serde_json::json;
146///
147/// async fn redact_secrets(event: PostToolUseEvent) -> Option<HookDecision> {
148///     // Redact API keys from Read tool output
149///     if event.tool_name == "Read" {
150///         if let Some(content) = event.tool_result.get("content") {
151///             if let Some(text) = content.as_str() {
152///                 if text.contains("API_KEY=") {
153///                     let redacted = text.replace(
154///                         |c: char| c.is_alphanumeric(),
155///                         "*"
156///                     );
157///                     // Note: PostToolUse hooks typically don't modify results,
158///                     // but you could log this for security review
159///                     println!("Warning: Potential API key detected in output");
160///                 }
161///             }
162///         }
163///     }
164///     None
165/// }
166/// ```
167///
168/// # Note on Modification
169///
170/// While `HookDecision` theoretically allows modification in PostToolUse hooks, this is
171/// rarely used in practice. The tool has already executed, and most agents don't support
172/// modifying historical results. PostToolUse hooks are primarily for observation and auditing.
173#[derive(Debug, Clone)]
174pub struct PostToolUseEvent {
175    /// Name of the tool that was executed
176    pub tool_name: String,
177    /// Input parameters that were actually used (may differ from original if modified by PreToolUse)
178    pub tool_input: Value,
179    /// Unique identifier for this tool use (correlates with PreToolUseEvent)
180    pub tool_use_id: String,
181    /// Result returned by the tool - may contain "content" on success or "error" on failure
182    pub tool_result: Value,
183    /// Structured JSON snapshot including this tool call and its unmodified result.
184    pub history: Vec<Value>,
185}
186
187impl PostToolUseEvent {
188    /// Creates a new PostToolUseEvent.
189    ///
190    /// This constructor is typically called by the agent runtime after tool execution,
191    /// not by user code. Users receive instances of this struct in their hook handlers.
192    pub fn new(
193        tool_name: String,
194        tool_input: Value,
195        tool_use_id: String,
196        tool_result: Value,
197        history: Vec<Value>,
198    ) -> Self {
199        Self {
200            tool_name,
201            tool_input,
202            tool_use_id,
203            tool_result,
204            history,
205        }
206    }
207}
208
209/// Event fired **before** processing user input, enabling content moderation and prompt enhancement.
210///
211/// This event is triggered whenever a user submits a prompt to the agent, before the agent
212/// begins processing it. Use this to implement content moderation, add context, inject
213/// instructions, or track user interactions.
214///
215/// # Use Cases
216///
217/// - **Content moderation**: Filter inappropriate or harmful user inputs
218/// - **Prompt enhancement**: Add system context, timestamps, or user information
219/// - **Input validation**: Ensure prompts meet format or length requirements
220/// - **Usage tracking**: Log user interactions for analytics or billing
221/// - **Context injection**: Add relevant background information to every prompt
222///
223/// # Fields
224///
225/// - `prompt`: The user's original input text
226/// - `history`: Read-only snapshot of the conversation history before this prompt
227///
228/// # Example: Content Moderation
229///
230/// ```rust
231/// use open_agent::{UserPromptSubmitEvent, HookDecision};
232///
233/// async fn content_moderator(event: UserPromptSubmitEvent) -> Option<HookDecision> {
234///     // Block prompts containing banned words
235///     let banned_words = ["spam", "malware", "hack"];
236///
237///     for word in banned_words {
238///         if event.prompt.to_lowercase().contains(word) {
239///             return Some(HookDecision::block(
240///                 format!("Content policy violation: contains '{}'", word)
241///             ));
242///         }
243///     }
244///     None // Allow clean prompts
245/// }
246/// ```
247///
248/// # Example: Automatic Context Enhancement
249///
250/// ```rust
251/// use open_agent::{UserPromptSubmitEvent, HookDecision};
252///
253/// async fn add_context(event: UserPromptSubmitEvent) -> Option<HookDecision> {
254///     // Add helpful context to every user prompt
255///     let enhanced = format!(
256///         "{}\n\n---\nContext: User timezone is UTC, current session started at 2025-11-07",
257///         event.prompt
258///     );
259///
260///     Some(HookDecision::modify_prompt(
261///         enhanced,
262///         "Added session context"
263///     ))
264/// }
265/// ```
266///
267/// # Example: Usage Tracking
268///
269/// ```rust
270/// use open_agent::{UserPromptSubmitEvent, HookDecision};
271///
272/// async fn track_usage(event: UserPromptSubmitEvent) -> Option<HookDecision> {
273///     // Log every user interaction for analytics
274///     println!(
275///         "[ANALYTICS] User submitted prompt of {} characters at history depth {}",
276///         event.prompt.len(),
277///         event.history.len()
278///     );
279///
280///     // Could also:
281///     // - Update usage quotas
282///     // - Send to analytics service
283///     // - Check rate limits
284///
285///     None // Don't modify the prompt
286/// }
287/// ```
288///
289/// # Modification Behavior
290///
291/// If you return `HookDecision::modify_prompt()`, the modified prompt completely replaces
292/// the original user input before the agent processes it. This is powerful but should be
293/// used carefully to avoid confusing the user or the agent.
294#[derive(Debug, Clone)]
295pub struct UserPromptSubmitEvent {
296    /// The user's original input prompt text
297    pub prompt: String,
298    /// Structured JSON snapshot of history before this prompt is added.
299    pub history: Vec<Value>,
300}
301
302impl UserPromptSubmitEvent {
303    /// Creates a new UserPromptSubmitEvent.
304    ///
305    /// This constructor is typically called by the agent runtime when processing user input,
306    /// not by user code. Users receive instances of this struct in their hook handlers.
307    pub fn new(prompt: String, history: Vec<Value>) -> Self {
308        Self { prompt, history }
309    }
310}