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}