1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use crate::types::message::Message;
7
8pub type HookEvent = String;
10
11pub type HookInput = HashMap<String, serde_json::Value>;
13
14pub type PermissionUpdate = HashMap<String, serde_json::Value>;
16
17pub type HookJSONOutput = serde_json::Value;
19
20pub type AsyncHookJSONOutput = serde_json::Value;
22
23pub type SyncHookJSONOutput = serde_json::Value;
25
26pub fn is_hook_event(value: &str) -> bool {
28 let events = [
30 "PreToolUse",
31 "UserPromptSubmit",
32 "SessionStart",
33 "Setup",
34 "SubagentStart",
35 "PostToolUse",
36 "PostToolUseFailure",
37 "PermissionDenied",
38 "Notification",
39 "PermissionRequest",
40 "Elicitation",
41 "ElicitationResult",
42 "CwdChanged",
43 "FileChanged",
44 "WorktreeCreate",
45 ];
46 events.contains(&value)
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct PromptRequest {
52 pub prompt: String,
54 pub message: String,
55 pub options: Vec<PromptOption>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct PromptOption {
61 pub key: String,
62 pub label: String,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub description: Option<String>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct PromptResponse {
70 #[serde(rename = "prompt_response")]
72 pub prompt_response: String,
73 pub selected: String,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct SyncHookResponse {
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub continue_flag: Option<bool>,
82 #[serde(skip_serializing_if = "Option::is_none")]
84 #[serde(rename = "suppressOutput")]
85 pub suppress_output: Option<bool>,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 #[serde(rename = "stopReason")]
89 pub stop_reason: Option<String>,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub decision: Option<HookDecision>,
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub reason: Option<String>,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 #[serde(rename = "systemMessage")]
98 pub system_message: Option<String>,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 #[serde(rename = "hookSpecificOutput")]
102 pub hook_specific_output: Option<HookSpecificOutput>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107#[serde(rename_all = "lowercase")]
108pub enum HookDecision {
109 Approve,
110 Block,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115#[serde(tag = "hookEventName")]
116pub enum HookSpecificOutput {
117 #[serde(rename = "PreToolUse")]
118 PreToolUse {
119 #[serde(skip_serializing_if = "Option::is_none")]
120 #[serde(rename = "permissionDecision")]
121 permission_decision: Option<String>,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 #[serde(rename = "permissionDecisionReason")]
124 permission_decision_reason: Option<String>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 #[serde(rename = "updatedInput")]
127 updated_input: Option<HashMap<String, serde_json::Value>>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 #[serde(rename = "additionalContext")]
130 additional_context: Option<String>,
131 },
132 #[serde(rename = "UserPromptSubmit")]
133 UserPromptSubmit {
134 #[serde(skip_serializing_if = "Option::is_none")]
135 #[serde(rename = "additionalContext")]
136 additional_context: Option<String>,
137 },
138 #[serde(rename = "SessionStart")]
139 SessionStart {
140 #[serde(skip_serializing_if = "Option::is_none")]
141 #[serde(rename = "additionalContext")]
142 additional_context: Option<String>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 #[serde(rename = "initialUserMessage")]
145 initial_user_message: Option<String>,
146 #[serde(skip_serializing_if = "Option::is_none")]
148 #[serde(rename = "watchPaths")]
149 watch_paths: Option<Vec<String>>,
150 },
151 #[serde(rename = "Setup")]
152 Setup {
153 #[serde(skip_serializing_if = "Option::is_none")]
154 #[serde(rename = "additionalContext")]
155 additional_context: Option<String>,
156 },
157 #[serde(rename = "SubagentStart")]
158 SubagentStart {
159 #[serde(skip_serializing_if = "Option::is_none")]
160 #[serde(rename = "additionalContext")]
161 additional_context: Option<String>,
162 },
163 #[serde(rename = "PostToolUse")]
164 PostToolUse {
165 #[serde(skip_serializing_if = "Option::is_none")]
166 #[serde(rename = "additionalContext")]
167 additional_context: Option<String>,
168 #[serde(skip_serializing_if = "Option::is_none")]
170 #[serde(rename = "updatedMCPToolOutput")]
171 updated_mcp_tool_output: Option<serde_json::Value>,
172 },
173 #[serde(rename = "PostToolUseFailure")]
174 PostToolUseFailure {
175 #[serde(skip_serializing_if = "Option::is_none")]
176 #[serde(rename = "additionalContext")]
177 additional_context: Option<String>,
178 },
179 #[serde(rename = "PermissionDenied")]
180 PermissionDenied {
181 #[serde(skip_serializing_if = "Option::is_none")]
182 retry: Option<bool>,
183 },
184 #[serde(rename = "Notification")]
185 Notification {
186 #[serde(skip_serializing_if = "Option::is_none")]
187 #[serde(rename = "additionalContext")]
188 additional_context: Option<String>,
189 },
190 #[serde(rename = "PermissionRequest")]
191 PermissionRequest {
192 #[serde(flatten)]
193 decision: PermissionRequestDecision,
194 },
195 #[serde(rename = "Elicitation")]
196 Elicitation {
197 #[serde(skip_serializing_if = "Option::is_none")]
198 action: Option<ElicitationAction>,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 content: Option<HashMap<String, serde_json::Value>>,
201 },
202 #[serde(rename = "ElicitationResult")]
203 ElicitationResult {
204 #[serde(skip_serializing_if = "Option::is_none")]
205 action: Option<ElicitationAction>,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 content: Option<HashMap<String, serde_json::Value>>,
208 },
209 #[serde(rename = "CwdChanged")]
210 CwdChanged {
211 #[serde(skip_serializing_if = "Option::is_none")]
213 #[serde(rename = "watchPaths")]
214 watch_paths: Option<Vec<String>>,
215 },
216 #[serde(rename = "FileChanged")]
217 FileChanged {
218 #[serde(skip_serializing_if = "Option::is_none")]
220 #[serde(rename = "watchPaths")]
221 watch_paths: Option<Vec<String>>,
222 },
223 #[serde(rename = "WorktreeCreate")]
224 WorktreeCreate {
225 #[serde(rename = "worktreePath")]
226 worktree_path: String,
227 },
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232#[serde(tag = "behavior")]
233pub enum PermissionRequestDecision {
234 #[serde(rename = "allow")]
235 Allow {
236 #[serde(skip_serializing_if = "Option::is_none")]
237 #[serde(rename = "updatedInput")]
238 updated_input: Option<HashMap<String, serde_json::Value>>,
239 #[serde(skip_serializing_if = "Option::is_none")]
240 #[serde(rename = "updatedPermissions")]
241 updated_permissions: Option<Vec<PermissionUpdate>>,
242 },
243 #[serde(rename = "deny")]
244 Deny {
245 #[serde(skip_serializing_if = "Option::is_none")]
246 message: Option<String>,
247 #[serde(skip_serializing_if = "Option::is_none")]
248 interrupt: Option<bool>,
249 },
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
254#[serde(rename_all = "lowercase")]
255pub enum ElicitationAction {
256 Accept,
257 Decline,
258 Cancel,
259}
260
261pub fn is_sync_hook_json_output(json: &HookJSONOutput) -> bool {
263 !json.get("async").is_some_and(|v| v.as_bool() == Some(true))
264}
265
266pub fn is_async_hook_json_output(json: &HookJSONOutput) -> bool {
268 json.get("async").is_some_and(|v| v.as_bool() == Some(true))
269}
270
271pub struct HookCallbackContext {
273 pub get_app_state: Box<dyn Fn() -> Box<dyn std::any::Any> + Send + Sync>,
274 pub update_attribution_state:
275 Box<dyn Fn(Box<dyn std::any::Any>) -> Box<dyn std::any::Any> + Send + Sync>,
276}
277
278pub struct HookCallback {
280 pub callback_type: String, pub callback: Box<
282 dyn Fn(
283 HookInput,
284 Option<String>, Option<tokio::sync::oneshot::Receiver<()>>, Option<usize>, Option<HookCallbackContext>,
288 )
289 -> std::pin::Pin<Box<dyn std::future::Future<Output = HookJSONOutput> + Send>>
290 + Send
291 + Sync,
292 >,
293 pub timeout: Option<u64>,
295 pub internal: Option<bool>,
297}
298
299pub struct HookCallbackMatcher {
301 pub matcher: Option<String>,
302 pub hooks: Vec<HookCallback>,
303 pub plugin_name: Option<String>,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct HookProgress {
309 #[serde(rename = "type")]
310 pub entry_type: String, #[serde(rename = "hookEvent")]
312 pub hook_event: HookEvent,
313 #[serde(rename = "hookName")]
314 pub hook_name: String,
315 pub command: String,
316 #[serde(skip_serializing_if = "Option::is_none")]
317 #[serde(rename = "promptText")]
318 pub prompt_text: Option<String>,
319 #[serde(skip_serializing_if = "Option::is_none")]
320 #[serde(rename = "statusMessage")]
321 pub status_message: Option<String>,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct HookBlockingError {
327 #[serde(rename = "blockingError")]
328 pub blocking_error: String,
329 pub command: String,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334#[serde(tag = "behavior")]
335pub enum PermissionRequestResult {
336 #[serde(rename = "allow")]
337 Allow {
338 #[serde(skip_serializing_if = "Option::is_none")]
339 #[serde(rename = "updatedInput")]
340 updated_input: Option<HashMap<String, serde_json::Value>>,
341 #[serde(skip_serializing_if = "Option::is_none")]
342 #[serde(rename = "updatedPermissions")]
343 updated_permissions: Option<Vec<PermissionUpdate>>,
344 },
345 #[serde(rename = "deny")]
346 Deny {
347 #[serde(skip_serializing_if = "Option::is_none")]
348 message: Option<String>,
349 #[serde(skip_serializing_if = "Option::is_none")]
350 interrupt: Option<bool>,
351 },
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct HookResult {
357 #[serde(skip_serializing_if = "Option::is_none")]
358 pub message: Option<Message>,
359 #[serde(skip_serializing_if = "Option::is_none")]
360 #[serde(rename = "systemMessage")]
361 pub system_message: Option<Message>,
362 #[serde(skip_serializing_if = "Option::is_none")]
363 #[serde(rename = "blockingError")]
364 pub blocking_error: Option<HookBlockingError>,
365 pub outcome: HookOutcome,
366 #[serde(skip_serializing_if = "Option::is_none")]
367 #[serde(rename = "preventContinuation")]
368 pub prevent_continuation: Option<bool>,
369 #[serde(skip_serializing_if = "Option::is_none")]
370 #[serde(rename = "stopReason")]
371 pub stop_reason: Option<String>,
372 #[serde(skip_serializing_if = "Option::is_none")]
373 #[serde(rename = "permissionBehavior")]
374 pub permission_behavior: Option<PermissionBehavior>,
375 #[serde(skip_serializing_if = "Option::is_none")]
376 #[serde(rename = "hookPermissionDecisionReason")]
377 pub hook_permission_decision_reason: Option<String>,
378 #[serde(skip_serializing_if = "Option::is_none")]
379 #[serde(rename = "additionalContext")]
380 pub additional_context: Option<String>,
381 #[serde(skip_serializing_if = "Option::is_none")]
382 #[serde(rename = "initialUserMessage")]
383 pub initial_user_message: Option<String>,
384 #[serde(skip_serializing_if = "Option::is_none")]
385 #[serde(rename = "updatedInput")]
386 pub updated_input: Option<HashMap<String, serde_json::Value>>,
387 #[serde(skip_serializing_if = "Option::is_none")]
388 #[serde(rename = "updatedMCPToolOutput")]
389 pub updated_mcp_tool_output: Option<serde_json::Value>,
390 #[serde(skip_serializing_if = "Option::is_none")]
391 #[serde(rename = "permissionRequestResult")]
392 pub permission_request_result: Option<PermissionRequestResult>,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 pub retry: Option<bool>,
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
399#[serde(rename_all = "snake_case")]
400pub enum HookOutcome {
401 Success,
402 Blocking,
403 NonBlockingError,
404 Cancelled,
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
409#[serde(rename_all = "lowercase")]
410pub enum PermissionBehavior {
411 Ask,
412 Deny,
413 Allow,
414 Passthrough,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct AggregatedHookResult {
420 #[serde(skip_serializing_if = "Option::is_none")]
421 pub message: Option<Message>,
422 #[serde(skip_serializing_if = "Option::is_none")]
423 #[serde(rename = "blockingErrors")]
424 pub blocking_errors: Option<Vec<HookBlockingError>>,
425 #[serde(skip_serializing_if = "Option::is_none")]
426 #[serde(rename = "preventContinuation")]
427 pub prevent_continuation: Option<bool>,
428 #[serde(skip_serializing_if = "Option::is_none")]
429 #[serde(rename = "stopReason")]
430 pub stop_reason: Option<String>,
431 #[serde(skip_serializing_if = "Option::is_none")]
432 #[serde(rename = "hookPermissionDecisionReason")]
433 pub hook_permission_decision_reason: Option<String>,
434 #[serde(skip_serializing_if = "Option::is_none")]
435 #[serde(rename = "permissionBehavior")]
436 pub permission_behavior: Option<String>,
437 #[serde(skip_serializing_if = "Option::is_none")]
438 #[serde(rename = "additionalContexts")]
439 pub additional_contexts: Option<Vec<String>>,
440 #[serde(skip_serializing_if = "Option::is_none")]
441 #[serde(rename = "initialUserMessage")]
442 pub initial_user_message: Option<String>,
443 #[serde(skip_serializing_if = "Option::is_none")]
444 #[serde(rename = "updatedInput")]
445 pub updated_input: Option<HashMap<String, serde_json::Value>>,
446 #[serde(skip_serializing_if = "Option::is_none")]
447 #[serde(rename = "updatedMCPToolOutput")]
448 pub updated_mcp_tool_output: Option<serde_json::Value>,
449 #[serde(skip_serializing_if = "Option::is_none")]
450 #[serde(rename = "permissionRequestResult")]
451 pub permission_request_result: Option<PermissionRequestResult>,
452 #[serde(skip_serializing_if = "Option::is_none")]
453 pub retry: Option<bool>,
454}