Skip to main content

polyhook_core/
events.rs

1use crate::types::CallerKind;
2
3/// Normalize a vendor-specific event name to the canonical polyhook event name.
4///
5/// If no mapping exists the original name is returned unchanged.
6pub fn normalize_event(vendor: &str, caller: &CallerKind) -> String {
7    let canonical = match caller {
8        CallerKind::ClaudeCode => normalize_claude_code_event(vendor),
9        CallerKind::Cursor => normalize_cursor_event(vendor),
10        CallerKind::Windsurf => normalize_windsurf_event(vendor),
11        CallerKind::Cline => normalize_cline_event(vendor),
12        CallerKind::Amp => normalize_amp_event(vendor),
13        CallerKind::GeminiCli => normalize_gemini_cli_event(vendor),
14        CallerKind::Hermes => normalize_hermes_event(vendor),
15        CallerKind::Pi | CallerKind::Codex => normalize_claude_code_event(vendor),
16        CallerKind::Unknown => None,
17    };
18
19    canonical
20        .map(str::to_owned)
21        .unwrap_or_else(|| vendor.to_owned())
22}
23
24fn normalize_claude_code_event(vendor: &str) -> Option<&'static str> {
25    match vendor {
26        "PreToolUse" => Some("tool:before"),
27        "PostToolUse" => Some("tool:after"),
28        "Startup" => Some("session:start"),
29        "Stop" => Some("session:stop"),
30        "SubagentStop" => Some("agent:stop"),
31        "UserPromptSubmit" => Some("prompt:submit"),
32        "Notification" => Some("notification"),
33        _ => None,
34    }
35}
36
37fn normalize_cursor_event(vendor: &str) -> Option<&'static str> {
38    match vendor {
39        "BeforeToolCall" => Some("tool:before"),
40        "AfterToolCall" => Some("tool:after"),
41        "SessionStart" => Some("session:start"),
42        "SessionEnd" => Some("session:stop"),
43        "Notification" => Some("notification"),
44        _ => None,
45    }
46}
47
48fn normalize_windsurf_event(vendor: &str) -> Option<&'static str> {
49    match vendor {
50        "pre_tool" => Some("tool:before"),
51        "post_tool" => Some("tool:after"),
52        "session_start" => Some("session:start"),
53        "session_end" => Some("session:stop"),
54        "notification" => Some("notification"),
55        _ => None,
56    }
57}
58
59fn normalize_cline_event(vendor: &str) -> Option<&'static str> {
60    match vendor {
61        "beforeToolUse" => Some("tool:before"),
62        "afterToolUse" => Some("tool:after"),
63        "onStart" => Some("session:start"),
64        "onStop" => Some("session:stop"),
65        "UserPromptSubmit" => Some("prompt:submit"),
66        _ => None,
67    }
68}
69
70fn normalize_amp_event(vendor: &str) -> Option<&'static str> {
71    match vendor {
72        "tool.before" => Some("tool:before"),
73        "tool.after" => Some("tool:after"),
74        "session.start" => Some("session:start"),
75        "session.stop" => Some("session:stop"),
76        "agent.stop" => Some("agent:stop"),
77        _ => None,
78    }
79}
80
81fn normalize_gemini_cli_event(vendor: &str) -> Option<&'static str> {
82    match vendor {
83        "BeforeTool" => Some("tool:before"),
84        "AfterTool" => Some("tool:after"),
85        "SessionStart" => Some("session:start"),
86        "SessionEnd" => Some("session:stop"),
87        "AfterAgent" => Some("agent:stop"),
88        "BeforeAgent" => Some("prompt:submit"),
89        "Notification" => Some("notification"),
90        _ => None,
91    }
92}
93
94fn normalize_hermes_event(vendor: &str) -> Option<&'static str> {
95    match vendor {
96        "pre_tool_call" => Some("tool:before"),
97        "post_tool_call" => Some("tool:after"),
98        "on_session_start" => Some("session:start"),
99        "on_session_end" | "on_session_finalize" => Some("session:stop"),
100        "subagent_stop" => Some("agent:stop"),
101        // pre_llm_call fires once per user turn and can inject context.
102        "pre_llm_call" => Some("prompt:submit"),
103        _ => None,
104    }
105}
106
107// ---------------------------------------------------------------------------
108// Tests
109// ---------------------------------------------------------------------------
110
111#[cfg(test)]
112#[path = "events_tests.rs"]
113mod tests;