ricecoder_hooks/types.rs
1//! Core data types for the hooks system
2//!
3//! This module defines the core data structures for the hooks system, including hooks,
4//! actions, events, and execution results.
5//!
6//! # Examples
7//!
8//! Creating a simple command hook:
9//!
10//! ```ignore
11//! use ricecoder_hooks::*;
12//!
13//! let hook = Hook {
14//! id: "format-on-save".to_string(),
15//! name: "Format on Save".to_string(),
16//! description: Some("Format code when file is saved".to_string()),
17//! event: "file_saved".to_string(),
18//! action: Action::Command(CommandAction {
19//! command: "prettier".to_string(),
20//! args: vec!["--write".to_string(), "{{file_path}}".to_string()],
21//! timeout_ms: Some(5000),
22//! capture_output: true,
23//! }),
24//! enabled: true,
25//! tags: vec!["formatting".to_string()],
26//! metadata: serde_json::json!({}),
27//! condition: None,
28//! };
29//! ```
30
31use serde::{Deserialize, Serialize};
32use std::collections::HashMap;
33
34/// A hook that triggers on specific events
35///
36/// Hooks are the core building blocks of the hooks system. Each hook is associated with
37/// an event type and defines an action to execute when that event occurs.
38///
39/// # Fields
40///
41/// * `id` - Unique identifier for the hook (typically a UUID)
42/// * `name` - Human-readable name for the hook
43/// * `description` - Optional description of what the hook does
44/// * `event` - Event type that triggers this hook (e.g., "file_saved", "test_passed")
45/// * `action` - Action to execute when the hook is triggered
46/// * `enabled` - Whether the hook is currently enabled
47/// * `tags` - Tags for categorizing and filtering hooks
48/// * `metadata` - Additional metadata stored as JSON
49/// * `condition` - Optional condition that must be met for the hook to execute
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct Hook {
52 /// Unique identifier for the hook
53 pub id: String,
54
55 /// Human-readable name
56 pub name: String,
57
58 /// Optional description
59 pub description: Option<String>,
60
61 /// Event that triggers this hook
62 pub event: String,
63
64 /// Action to execute
65 pub action: Action,
66
67 /// Whether the hook is enabled
68 pub enabled: bool,
69
70 /// Tags for categorizing hooks
71 pub tags: Vec<String>,
72
73 /// Additional metadata
74 pub metadata: serde_json::Value,
75
76 /// Optional condition for execution
77 pub condition: Option<Condition>,
78}
79
80/// Action to execute when a hook is triggered
81///
82/// Actions define what happens when a hook is triggered. There are four types of actions:
83///
84/// * `Command` - Execute a shell command
85/// * `ToolCall` - Call a ricecoder tool with parameters
86/// * `AiPrompt` - Send a prompt to an AI assistant
87/// * `Chain` - Execute multiple hooks in sequence
88///
89/// # Examples
90///
91/// Command action:
92/// ```ignore
93/// Action::Command(CommandAction {
94/// command: "echo".to_string(),
95/// args: vec!["Hello, World!".to_string()],
96/// timeout_ms: Some(5000),
97/// capture_output: true,
98/// })
99/// ```
100///
101/// Tool call action:
102/// ```ignore
103/// Action::ToolCall(ToolCallAction {
104/// tool_name: "formatter".to_string(),
105/// tool_path: "/usr/local/bin/prettier".to_string(),
106/// parameters: ParameterBindings {
107/// bindings: vec![
108/// ("file_path".to_string(), ParameterValue::Variable("file_path".to_string())),
109/// ].into_iter().collect(),
110/// },
111/// timeout_ms: Some(5000),
112/// })
113/// ```
114#[derive(Debug, Clone, Serialize, Deserialize)]
115#[serde(tag = "type")]
116pub enum Action {
117 /// Execute a shell command
118 #[serde(rename = "command")]
119 Command(CommandAction),
120
121 /// Call a tool with parameters
122 #[serde(rename = "tool_call")]
123 ToolCall(ToolCallAction),
124
125 /// Send a prompt to an AI assistant
126 #[serde(rename = "ai_prompt")]
127 AiPrompt(AiPromptAction),
128
129 /// Chain multiple hooks
130 #[serde(rename = "chain")]
131 Chain(ChainAction),
132}
133
134/// Command action configuration
135///
136/// Executes a shell command when the hook is triggered. Supports variable substitution
137/// in command arguments using `{{variable_name}}` syntax.
138///
139/// # Examples
140///
141/// ```ignore
142/// CommandAction {
143/// command: "prettier".to_string(),
144/// args: vec!["--write".to_string(), "{{file_path}}".to_string()],
145/// timeout_ms: Some(5000),
146/// capture_output: true,
147/// }
148/// ```
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct CommandAction {
151 /// Command to execute
152 pub command: String,
153
154 /// Command arguments (supports variable substitution)
155 pub args: Vec<String>,
156
157 /// Optional timeout in milliseconds
158 pub timeout_ms: Option<u64>,
159
160 /// Whether to capture output
161 pub capture_output: bool,
162}
163
164/// Tool call action configuration
165///
166/// Calls a ricecoder tool with parameters bound from the event context. The tool path
167/// can be an absolute path, relative path, or internal handler reference.
168///
169/// # Examples
170///
171/// ```ignore
172/// ToolCallAction {
173/// tool_name: "code_formatter".to_string(),
174/// tool_path: "/usr/local/bin/prettier".to_string(),
175/// parameters: ParameterBindings {
176/// bindings: vec![
177/// ("file_path".to_string(), ParameterValue::Variable("file_path".to_string())),
178/// ("format".to_string(), ParameterValue::Literal(json!("json"))),
179/// ].into_iter().collect(),
180/// },
181/// timeout_ms: Some(5000),
182/// }
183/// ```
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct ToolCallAction {
186 /// Name of the tool
187 pub tool_name: String,
188
189 /// Path to the tool (absolute, relative, or internal handler)
190 pub tool_path: String,
191
192 /// Parameter bindings from event context
193 pub parameters: ParameterBindings,
194
195 /// Optional timeout in milliseconds
196 pub timeout_ms: Option<u64>,
197}
198
199/// Parameter bindings for tool calls
200///
201/// Maps parameter names to values that can be either literals or references to
202/// event context variables.
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct ParameterBindings {
205 /// Map of parameter names to values
206 pub bindings: HashMap<String, ParameterValue>,
207}
208
209/// Parameter value (literal or variable reference)
210///
211/// Parameters can be either literal values or references to event context variables.
212/// Variable references use the format `{{variable_name}}` and are substituted at
213/// execution time.
214///
215/// # Examples
216///
217/// Literal value:
218/// ```ignore
219/// ParameterValue::Literal(json!("json"))
220/// ```
221///
222/// Variable reference:
223/// ```ignore
224/// ParameterValue::Variable("file_path".to_string())
225/// ```
226#[derive(Debug, Clone, Serialize, Deserialize)]
227#[serde(untagged)]
228pub enum ParameterValue {
229 /// Literal value
230 Literal(serde_json::Value),
231
232 /// Variable reference (substituted from event context)
233 Variable(String),
234}
235
236/// AI prompt action configuration
237///
238/// Sends a prompt to an AI assistant with variables substituted from the event context.
239/// Supports streaming responses and custom model configuration.
240///
241/// # Examples
242///
243/// ```ignore
244/// AiPromptAction {
245/// prompt_template: "Format the following code:\n{{code}}".to_string(),
246/// variables: vec![
247/// ("code".to_string(), "file_content".to_string()),
248/// ].into_iter().collect(),
249/// model: Some("gpt-4".to_string()),
250/// temperature: Some(0.7),
251/// max_tokens: Some(2000),
252/// stream: true,
253/// }
254/// ```
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct AiPromptAction {
257 /// Prompt template with variable placeholders
258 pub prompt_template: String,
259
260 /// Variables for substitution (maps placeholder to context key)
261 pub variables: HashMap<String, String>,
262
263 /// Optional model name
264 pub model: Option<String>,
265
266 /// Optional temperature (0.0 to 2.0)
267 pub temperature: Option<f32>,
268
269 /// Optional max tokens for response
270 pub max_tokens: Option<u32>,
271
272 /// Whether to stream responses
273 pub stream: bool,
274}
275
276/// Chain action configuration
277///
278/// Executes multiple hooks in sequence. Optionally passes the output of one hook
279/// as context to the next hook in the chain.
280///
281/// # Examples
282///
283/// ```ignore
284/// ChainAction {
285/// hook_ids: vec![
286/// "analyze-code".to_string(),
287/// "generate-suggestions".to_string(),
288/// "apply-suggestions".to_string(),
289/// ],
290/// pass_output: true,
291/// }
292/// ```
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct ChainAction {
295 /// IDs of hooks to execute in sequence
296 pub hook_ids: Vec<String>,
297
298 /// Whether to pass output between hooks
299 pub pass_output: bool,
300}
301
302/// Condition for hook execution
303///
304/// Optional condition that must be met for a hook to execute. Conditions are evaluated
305/// against the event context and can filter hooks based on context values.
306///
307/// # Examples
308///
309/// ```ignore
310/// Condition {
311/// expression: "file_path.ends_with('.rs')".to_string(),
312/// context_keys: vec!["file_path".to_string()],
313/// }
314/// ```
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct Condition {
317 /// Condition expression (evaluated against event context)
318 pub expression: String,
319
320 /// Context keys used in the expression
321 pub context_keys: Vec<String>,
322}
323
324/// Event that triggers hooks
325///
326/// Events are emitted by the system when something happens (e.g., file saved, test passed).
327/// Each event has a type, context, and timestamp.
328///
329/// # Examples
330///
331/// ```ignore
332/// Event {
333/// event_type: "file_saved".to_string(),
334/// context: EventContext {
335/// data: json!({
336/// "file_path": "/path/to/file.rs",
337/// "size": 1024,
338/// }),
339/// metadata: json!({
340/// "user": "alice",
341/// "project": "my-project",
342/// }),
343/// },
344/// timestamp: "2024-01-01T12:00:00Z".to_string(),
345/// }
346/// ```
347#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct Event {
349 /// Event type (e.g., "file_saved", "test_passed")
350 pub event_type: String,
351
352 /// Event context with data and metadata
353 pub context: EventContext,
354
355 /// Event timestamp (ISO 8601 format)
356 pub timestamp: String,
357}
358
359/// Context passed to hooks
360///
361/// Contains the data and metadata associated with an event. This context is passed
362/// to hooks and can be used for variable substitution in actions.
363#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct EventContext {
365 /// Event data (varies by event type)
366 pub data: serde_json::Value,
367
368 /// Event metadata (user, project, etc.)
369 pub metadata: serde_json::Value,
370}
371
372/// Result of hook execution
373///
374/// Contains the result of executing a hook, including status, output, and any errors.
375///
376/// # Examples
377///
378/// ```ignore
379/// HookResult {
380/// hook_id: "format-on-save".to_string(),
381/// status: HookStatus::Success,
382/// output: Some("Formatted 5 files".to_string()),
383/// error: None,
384/// duration_ms: 1234,
385/// }
386/// ```
387#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct HookResult {
389 /// Hook ID
390 pub hook_id: String,
391
392 /// Execution status
393 pub status: HookStatus,
394
395 /// Optional output from hook execution
396 pub output: Option<String>,
397
398 /// Optional error message
399 pub error: Option<String>,
400
401 /// Duration in milliseconds
402 pub duration_ms: u64,
403}
404
405/// Status of hook execution
406///
407/// Indicates the outcome of executing a hook.
408#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
409#[serde(rename_all = "lowercase")]
410pub enum HookStatus {
411 /// Hook executed successfully
412 Success,
413
414 /// Hook execution failed
415 Failed,
416
417 /// Hook execution timed out
418 Timeout,
419
420 /// Hook was skipped (condition not met or hook disabled)
421 Skipped,
422}