forked from
anil.recoil.org/monopam-myspace
My aggregated monorepo of OCaml code, automaintained
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** Fully typed hook callbacks.
7
8 Hooks allow you to intercept and control events in Claude Code sessions,
9 using fully typed OCaml values instead of raw JSON.
10
11 {1 Overview}
12
13 This module provides a high-level, type-safe interface to hooks. Each hook
14 type has:
15 - Fully typed input records using {!Tool_input.t}
16 - Fully typed output records
17 - Helper functions for common responses
18 - Conversion functions to/from wire format ({!Proto.Hooks})
19
20 {1 Example Usage}
21
22 {[
23 open Eio.Std
24
25 (* Block dangerous bash commands *)
26 let block_rm_rf input =
27 if input.Hooks.PreToolUse.tool_name = "Bash" then
28 match Tool_input.get_string input.tool_input "command" with
29 | Some cmd when String.contains cmd "rm -rf" ->
30 Hooks.PreToolUse.deny ~reason:"Dangerous command" ()
31 | _ -> Hooks.PreToolUse.continue ()
32 else Hooks.PreToolUse.continue ()
33
34 let hooks =
35 Hooks.empty
36 |> Hooks.on_pre_tool_use ~pattern:"Bash" block_rm_rf
37
38 let options = Claude.Options.create ~hooks () in
39 let client = Claude.Client.create ~options ~sw ~process_mgr () in
40 ]} *)
41
42val src : Logs.Src.t
43(** The log source for hooks *)
44
45(** {1 Hook Types} *)
46
47(** PreToolUse hook - fires before tool execution *)
48module PreToolUse : sig
49 (** {2 Input} *)
50
51 type input = {
52 session_id : string;
53 transcript_path : string;
54 tool_name : string;
55 tool_input : Tool_input.t;
56 }
57 (** Input provided to PreToolUse hooks. *)
58
59 (** {2 Output} *)
60
61 type decision =
62 | Allow
63 | Deny
64 | Ask
65 (** Permission decision for tool usage. *)
66
67 type output = {
68 decision : decision option;
69 reason : string option;
70 updated_input : Tool_input.t option;
71 }
72 (** Output from PreToolUse hooks. *)
73
74 (** {2 Response Builders} *)
75
76 val allow : ?reason:string -> ?updated_input:Tool_input.t -> unit -> output
77 (** [allow ?reason ?updated_input ()] creates an allow response.
78 @param reason Optional explanation for allowing
79 @param updated_input Optional modified tool input *)
80
81 val deny : ?reason:string -> unit -> output
82 (** [deny ?reason ()] creates a deny response.
83 @param reason Optional explanation for denying *)
84
85 val ask : ?reason:string -> unit -> output
86 (** [ask ?reason ()] creates an ask response to prompt the user.
87 @param reason Optional explanation for asking *)
88
89 val continue : unit -> output
90 (** [continue ()] creates a continue response with no decision. *)
91
92 (** {2 Callback Type} *)
93
94 type callback = input -> output
95 (** Callback function type for PreToolUse hooks. *)
96
97 (** {2 Conversion Functions} *)
98
99 val input_of_proto : Proto.Hooks.PreToolUse.Input.t -> input
100 (** [input_of_proto proto] converts wire format input to typed input. *)
101
102 val output_to_proto : output -> Proto.Hooks.PreToolUse.Output.t
103 (** [output_to_proto output] converts typed output to wire format. *)
104end
105
106(** PostToolUse hook - fires after tool execution *)
107module PostToolUse : sig
108 (** {2 Input} *)
109
110 type input = {
111 session_id : string;
112 transcript_path : string;
113 tool_name : string;
114 tool_input : Tool_input.t;
115 tool_response : Jsont.json; (* Response varies by tool *)
116 }
117 (** Input provided to PostToolUse hooks.
118 Note: [tool_response] remains as {!type:Jsont.json} since response schemas
119 vary by tool. *)
120
121 (** {2 Output} *)
122
123 type output = {
124 block : bool;
125 reason : string option;
126 additional_context : string option;
127 }
128 (** Output from PostToolUse hooks. *)
129
130 (** {2 Response Builders} *)
131
132 val continue : ?additional_context:string -> unit -> output
133 (** [continue ?additional_context ()] creates a continue response.
134 @param additional_context Optional context to add to the transcript *)
135
136 val block :
137 ?reason:string -> ?additional_context:string -> unit -> output
138 (** [block ?reason ?additional_context ()] creates a block response.
139 @param reason Optional explanation for blocking
140 @param additional_context Optional context to add to the transcript *)
141
142 (** {2 Callback Type} *)
143
144 type callback = input -> output
145 (** Callback function type for PostToolUse hooks. *)
146
147 (** {2 Conversion Functions} *)
148
149 val input_of_proto : Proto.Hooks.PostToolUse.Input.t -> input
150 (** [input_of_proto proto] converts wire format input to typed input. *)
151
152 val output_to_proto : output -> Proto.Hooks.PostToolUse.Output.t
153 (** [output_to_proto output] converts typed output to wire format. *)
154end
155
156(** UserPromptSubmit hook - fires when user submits a prompt *)
157module UserPromptSubmit : sig
158 (** {2 Input} *)
159
160 type input = {
161 session_id : string;
162 transcript_path : string;
163 prompt : string;
164 }
165 (** Input provided to UserPromptSubmit hooks. *)
166
167 (** {2 Output} *)
168
169 type output = {
170 block : bool;
171 reason : string option;
172 additional_context : string option;
173 }
174 (** Output from UserPromptSubmit hooks. *)
175
176 (** {2 Response Builders} *)
177
178 val continue : ?additional_context:string -> unit -> output
179 (** [continue ?additional_context ()] creates a continue response.
180 @param additional_context Optional context to add to the transcript *)
181
182 val block : ?reason:string -> unit -> output
183 (** [block ?reason ()] creates a block response.
184 @param reason Optional explanation for blocking *)
185
186 (** {2 Callback Type} *)
187
188 type callback = input -> output
189 (** Callback function type for UserPromptSubmit hooks. *)
190
191 (** {2 Conversion Functions} *)
192
193 val input_of_proto : Proto.Hooks.UserPromptSubmit.Input.t -> input
194 (** [input_of_proto proto] converts wire format input to typed input. *)
195
196 val output_to_proto : output -> Proto.Hooks.UserPromptSubmit.Output.t
197 (** [output_to_proto output] converts typed output to wire format. *)
198end
199
200(** Stop hook - fires when conversation stops *)
201module Stop : sig
202 (** {2 Input} *)
203
204 type input = {
205 session_id : string;
206 transcript_path : string;
207 stop_hook_active : bool;
208 }
209 (** Input provided to Stop hooks. *)
210
211 (** {2 Output} *)
212
213 type output = {
214 block : bool;
215 reason : string option;
216 }
217 (** Output from Stop hooks. *)
218
219 (** {2 Response Builders} *)
220
221 val continue : unit -> output
222 (** [continue ()] creates a continue response. *)
223
224 val block : ?reason:string -> unit -> output
225 (** [block ?reason ()] creates a block response.
226 @param reason Optional explanation for blocking *)
227
228 (** {2 Callback Type} *)
229
230 type callback = input -> output
231 (** Callback function type for Stop hooks. *)
232
233 (** {2 Conversion Functions} *)
234
235 val input_of_proto : Proto.Hooks.Stop.Input.t -> input
236 (** [input_of_proto proto] converts wire format input to typed input. *)
237
238 val output_to_proto : output -> Proto.Hooks.Stop.Output.t
239 (** [output_to_proto output] converts typed output to wire format. *)
240end
241
242(** SubagentStop hook - fires when a subagent stops *)
243module SubagentStop : sig
244 (** {2 Input} *)
245
246 type input = Stop.input
247 (** Same structure as Stop.input *)
248
249 (** {2 Output} *)
250
251 type output = Stop.output
252 (** Same structure as Stop.output *)
253
254 (** {2 Response Builders} *)
255
256 val continue : unit -> output
257 (** [continue ()] creates a continue response. *)
258
259 val block : ?reason:string -> unit -> output
260 (** [block ?reason ()] creates a block response.
261 @param reason Optional explanation for blocking *)
262
263 (** {2 Callback Type} *)
264
265 type callback = input -> output
266 (** Callback function type for SubagentStop hooks. *)
267
268 (** {2 Conversion Functions} *)
269
270 val input_of_proto : Proto.Hooks.SubagentStop.Input.t -> input
271 (** [input_of_proto proto] converts wire format input to typed input. *)
272
273 val output_to_proto : output -> Proto.Hooks.SubagentStop.Output.t
274 (** [output_to_proto output] converts typed output to wire format. *)
275end
276
277(** PreCompact hook - fires before message compaction *)
278module PreCompact : sig
279 (** {2 Input} *)
280
281 type input = {
282 session_id : string;
283 transcript_path : string;
284 }
285 (** Input provided to PreCompact hooks. *)
286
287 (** {2 Callback Type} *)
288
289 type callback = input -> unit
290 (** Callback function type for PreCompact hooks.
291 PreCompact hooks have no output - they are notification-only. *)
292
293 (** {2 Conversion Functions} *)
294
295 val input_of_proto : Proto.Hooks.PreCompact.Input.t -> input
296 (** [input_of_proto proto] converts wire format input to typed input. *)
297end
298
299(** {1 Hook Configuration} *)
300
301type t
302(** Hook configuration.
303
304 Hooks are configured using a builder pattern:
305 {[
306 Hooks.empty
307 |> Hooks.on_pre_tool_use ~pattern:"Bash" bash_handler
308 |> Hooks.on_post_tool_use post_handler
309 ]} *)
310
311val empty : t
312(** [empty] is an empty hook configuration with no callbacks. *)
313
314val on_pre_tool_use : ?pattern:string -> PreToolUse.callback -> t -> t
315(** [on_pre_tool_use ?pattern callback config] adds a PreToolUse hook.
316 @param pattern Optional regex pattern to match tool names (e.g., "Bash|Edit")
317 @param callback Function to invoke on matching events *)
318
319val on_post_tool_use : ?pattern:string -> PostToolUse.callback -> t -> t
320(** [on_post_tool_use ?pattern callback config] adds a PostToolUse hook.
321 @param pattern Optional regex pattern to match tool names
322 @param callback Function to invoke on matching events *)
323
324val on_user_prompt_submit : UserPromptSubmit.callback -> t -> t
325(** [on_user_prompt_submit callback config] adds a UserPromptSubmit hook.
326 @param callback Function to invoke on prompt submission *)
327
328val on_stop : Stop.callback -> t -> t
329(** [on_stop callback config] adds a Stop hook.
330 @param callback Function to invoke on conversation stop *)
331
332val on_subagent_stop : SubagentStop.callback -> t -> t
333(** [on_subagent_stop callback config] adds a SubagentStop hook.
334 @param callback Function to invoke on subagent stop *)
335
336val on_pre_compact : PreCompact.callback -> t -> t
337(** [on_pre_compact callback config] adds a PreCompact hook.
338 @param callback Function to invoke before message compaction *)
339
340(** {1 Internal - for client use} *)
341
342val get_callbacks :
343 t ->
344 (Proto.Hooks.event * (string option * (Jsont.json -> Proto.Hooks.result))
345 list)
346 list
347(** [get_callbacks config] returns hook configuration in format suitable for
348 registration with the CLI.
349
350 This function converts typed callbacks into wire format handlers that:
351 - Parse JSON input using Proto.Hooks types
352 - Convert to typed input using input_of_proto
353 - Invoke the user's typed callback
354 - Convert output back to wire format using output_to_proto
355
356 This is an internal function used by {!Client} - you should not need to
357 call it directly. *)