coding_agent_hooks/
input.rs1use std::io::Read;
8
9use serde::Deserialize;
10use tracing::{Level, instrument};
11
12use crate::AgentKind;
13
14#[derive(Debug, Clone, Deserialize)]
16#[serde(untagged)]
17pub enum HookInput {
18 ToolUse(ToolUseHookInput),
20 SessionStart(SessionStartHookInput),
22}
23
24#[derive(Debug, Clone, Deserialize, Default)]
29pub struct ToolUseHookInput {
30 pub session_id: String,
31 pub transcript_path: String,
32 pub cwd: String,
33 pub permission_mode: String,
34 pub hook_event_name: String,
35 pub tool_name: String,
36 pub tool_input: serde_json::Value,
37 pub tool_use_id: Option<String>,
38 #[serde(default)]
40 pub tool_response: Option<serde_json::Value>,
41
42 #[serde(skip)]
45 pub agent: Option<AgentKind>,
46 #[serde(skip)]
49 pub original_tool_name: Option<String>,
50}
51
52#[derive(Debug, Clone, Deserialize, Default)]
54pub struct SessionStartHookInput {
55 #[serde(default)]
56 pub session_id: String,
57 #[serde(default)]
58 pub transcript_path: String,
59 #[serde(default)]
60 pub cwd: String,
61 #[serde(default)]
62 pub permission_mode: Option<String>,
63 #[serde(default)]
64 pub hook_event_name: String,
65 #[serde(default)]
66 pub source: Option<String>,
67 #[serde(default)]
68 pub model: Option<String>,
69}
70
71impl SessionStartHookInput {
72 #[instrument(level = Level::TRACE, skip(reader))]
74 pub fn from_reader(reader: impl Read) -> anyhow::Result<Self> {
75 Ok(serde_json::from_reader(reader)?)
76 }
77}
78
79#[derive(Debug, Clone, Deserialize, Default)]
81pub struct StopHookInput {
82 #[serde(default)]
83 pub session_id: String,
84 #[serde(default)]
85 pub transcript_path: String,
86 #[serde(default)]
87 pub cwd: String,
88 #[serde(default)]
89 pub hook_event_name: String,
90}
91
92impl StopHookInput {
93 #[instrument(level = Level::TRACE, skip(reader))]
95 pub fn from_reader(reader: impl Read) -> anyhow::Result<Self> {
96 Ok(serde_json::from_reader(reader)?)
97 }
98}
99
100impl HookInput {
101 #[instrument(level = Level::TRACE, skip(reader))]
103 pub fn from_reader(reader: impl Read) -> anyhow::Result<Self> {
104 Ok(serde_json::from_reader(reader)?)
105 }
106
107 #[instrument(level = Level::TRACE)]
109 pub fn from_stdin() -> anyhow::Result<Self> {
110 Self::from_reader(std::io::stdin().lock())
111 }
112
113 pub fn hook_event_name(&self) -> &str {
115 match self {
116 HookInput::ToolUse(input) => &input.hook_event_name,
117 HookInput::SessionStart(input) => &input.hook_event_name,
118 }
119 }
120
121 pub fn session_id(&self) -> &str {
123 match self {
124 HookInput::ToolUse(input) => &input.session_id,
125 HookInput::SessionStart(input) => &input.session_id,
126 }
127 }
128
129 pub fn as_tool_use(&self) -> Option<&ToolUseHookInput> {
131 match self {
132 HookInput::ToolUse(input) => Some(input),
133 _ => None,
134 }
135 }
136
137 pub fn as_session_start(&self) -> Option<&SessionStartHookInput> {
139 match self {
140 HookInput::SessionStart(input) => Some(input),
141 _ => None,
142 }
143 }
144}
145
146impl ToolUseHookInput {
147 #[instrument(level = Level::TRACE, skip(reader))]
149 pub fn from_reader(reader: impl Read) -> anyhow::Result<Self> {
150 Ok(serde_json::from_reader(reader)?)
151 }
152}
153
154pub mod exit_code {
156 pub const SUCCESS: i32 = 0;
158 pub const BLOCKING_ERROR: i32 = 2;
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 fn sample_tool_use_json() -> &'static str {
167 r#"{
168 "session_id": "test-session",
169 "transcript_path": "/tmp/transcript.jsonl",
170 "cwd": "/home/user/project",
171 "permission_mode": "default",
172 "hook_event_name": "PreToolUse",
173 "tool_name": "Bash",
174 "tool_input": {"command": "git status", "timeout": 120000},
175 "tool_use_id": "toolu_01ABC"
176 }"#
177 }
178
179 #[test]
180 fn test_parse_tool_use_input() {
181 let input = HookInput::from_reader(sample_tool_use_json().as_bytes()).unwrap();
182 assert_eq!(input.session_id(), "test-session");
183 assert_eq!(input.hook_event_name(), "PreToolUse");
184
185 let tool_use = input.as_tool_use().expect("Should be ToolUse variant");
186 assert_eq!(tool_use.tool_name, "Bash");
187 }
188}