pub enum HookAction<T> {
Continue(T),
Cancel(String),
}
pub type Interceptor<T> = Box<dyn Fn(T) -> HookAction<T> + Send + Sync>;
#[derive(Debug, Clone)]
pub struct ExitEvent {
pub code: i32,
}
#[derive(Debug, Clone)]
pub struct ExecInput {
pub script: String,
}
#[derive(Debug, Clone)]
pub struct ExecOutput {
pub script: String,
pub stdout: String,
pub stderr: String,
pub exit_code: i32,
}
#[derive(Debug, Clone)]
pub struct ToolEvent {
pub name: String,
pub args: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ToolResult {
pub name: String,
pub stdout: String,
pub exit_code: i32,
}
#[derive(Debug, Clone)]
pub struct HttpRequestEvent {
pub method: String,
pub url: String,
pub headers: Vec<(String, String)>,
}
#[derive(Debug, Clone)]
pub struct HttpResponseEvent {
pub url: String,
pub status: u16,
pub headers: Vec<(String, String)>,
}
#[derive(Debug, Clone)]
pub struct ErrorEvent {
pub message: String,
}
#[derive(Default)]
pub struct Hooks {
pub(crate) on_exit: Vec<Interceptor<ExitEvent>>,
pub(crate) before_exec: Vec<Interceptor<ExecInput>>,
pub(crate) after_exec: Vec<Interceptor<ExecOutput>>,
pub(crate) before_tool: Vec<Interceptor<ToolEvent>>,
pub(crate) after_tool: Vec<Interceptor<ToolResult>>,
pub(crate) on_error: Vec<Interceptor<ErrorEvent>>,
}
impl Hooks {
pub(crate) fn fire_on_exit(&self, event: ExitEvent) -> Option<ExitEvent> {
fire_hooks(&self.on_exit, event)
}
pub(crate) fn fire_before_exec(&self, event: ExecInput) -> Option<ExecInput> {
fire_hooks(&self.before_exec, event)
}
pub(crate) fn fire_after_exec(&self, event: ExecOutput) -> Option<ExecOutput> {
fire_hooks(&self.after_exec, event)
}
pub(crate) fn fire_before_tool(&self, event: ToolEvent) -> Option<ToolEvent> {
fire_hooks(&self.before_tool, event)
}
pub(crate) fn fire_after_tool(&self, event: ToolResult) -> Option<ToolResult> {
fire_hooks(&self.after_tool, event)
}
pub(crate) fn fire_on_error(&self, event: ErrorEvent) -> Option<ErrorEvent> {
fire_hooks(&self.on_error, event)
}
pub fn has_hooks(&self) -> bool {
!self.on_exit.is_empty()
|| !self.before_exec.is_empty()
|| !self.after_exec.is_empty()
|| !self.before_tool.is_empty()
|| !self.after_tool.is_empty()
|| !self.on_error.is_empty()
}
}
fn fire_hooks<T>(hooks: &[Interceptor<T>], event: T) -> Option<T> {
if hooks.is_empty() {
return Some(event);
}
let mut current = event;
for hook in hooks {
match hook(current) {
HookAction::Continue(e) => current = e,
HookAction::Cancel(_) => return None,
}
}
Some(current)
}
impl std::fmt::Debug for Hooks {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Hooks")
.field("on_exit", &format!("{} hook(s)", self.on_exit.len()))
.field(
"before_exec",
&format!("{} hook(s)", self.before_exec.len()),
)
.field("after_exec", &format!("{} hook(s)", self.after_exec.len()))
.field(
"before_tool",
&format!("{} hook(s)", self.before_tool.len()),
)
.field("after_tool", &format!("{} hook(s)", self.after_tool.len()))
.field("on_error", &format!("{} hook(s)", self.on_error.len()))
.finish()
}
}