use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum LifecycleHook {
SessionStart,
UserPromptSubmit,
PostToolUse,
Stop,
SessionEnd,
}
impl LifecycleHook {
pub fn as_str(&self) -> &'static str {
match self {
LifecycleHook::SessionStart => "session_start",
LifecycleHook::UserPromptSubmit => "user_prompt_submit",
LifecycleHook::PostToolUse => "post_tool_use",
LifecycleHook::Stop => "stop",
LifecycleHook::SessionEnd => "session_end",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HookContext {
pub session_id: Option<String>,
pub workspace: Option<String>,
pub timestamp: String,
pub metadata: HashMap<String, serde_json::Value>,
}
impl HookContext {
pub fn new(session_id: Option<String>, workspace: Option<String>) -> Self {
Self {
session_id,
workspace,
timestamp: chrono::Utc::now().to_rfc3339(),
metadata: HashMap::new(),
}
}
pub fn with_metadata(mut self, key: String, value: serde_json::Value) -> Self {
self.metadata.insert(key, value);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum HookResult {
Continue,
Modify(serde_json::Value),
Abort { reason: String },
}
pub struct HookManager {
handlers: HashMap<
LifecycleHook,
Vec<Box<dyn Fn(LifecycleHook, &HookContext) -> crate::Result<HookResult> + Send + Sync>>,
>,
}
impl HookManager {
pub fn new() -> Self {
Self {
handlers: HashMap::new(),
}
}
pub fn register<F>(&mut self, hook: LifecycleHook, handler: F)
where
F: Fn(LifecycleHook, &HookContext) -> crate::Result<HookResult> + Send + Sync + 'static,
{
self.handlers
.entry(hook)
.or_default()
.push(Box::new(handler));
}
pub fn trigger(
&self,
hook: LifecycleHook,
context: &HookContext,
) -> crate::Result<Vec<HookResult>> {
let mut results = Vec::new();
if let Some(handlers) = self.handlers.get(&hook) {
for handler in handlers {
match handler(hook, context) {
Ok(HookResult::Abort { reason }) => {
return Ok(vec![HookResult::Abort { reason }]);
}
Ok(result) => results.push(result),
Err(e) => {
eprintln!("Hook handler error for {:?}: {}", hook, e);
}
}
}
}
Ok(results)
}
pub fn trigger_and_forget(&self, hook: LifecycleHook, context: HookContext) {
let _ = self.trigger(hook, &context);
}
}
impl Default for HookManager {
fn default() -> Self {
Self::new()
}
}
pub mod post_tool_use;
pub mod session_end;
pub mod session_start;
pub mod stop;
pub use post_tool_use::PostToolUseHandler;
pub use session_end::SessionEndHandler;
pub use session_start::SessionStartHandler;
pub use stop::StopHandler;