OCaml Claude SDK using Eio and Jsont
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 (** Permission decision for tool usage. *)
65
66 type output = {
67 decision : decision option;
68 reason : string option;
69 updated_input : Tool_input.t option;
70 }
71 (** Output from PreToolUse hooks. *)
72
73 (** {2 Response Builders} *)
74
75 val allow : ?reason:string -> ?updated_input:Tool_input.t -> unit -> output
76 (** [allow ?reason ?updated_input ()] creates an allow response.
77 @param reason Optional explanation for allowing
78 @param updated_input Optional modified tool input *)
79
80 val deny : ?reason:string -> unit -> output
81 (** [deny ?reason ()] creates a deny response.
82 @param reason Optional explanation for denying *)
83
84 val ask : ?reason:string -> unit -> output
85 (** [ask ?reason ()] creates an ask response to prompt the user.
86 @param reason Optional explanation for asking *)
87
88 val continue : unit -> output
89 (** [continue ()] creates a continue response with no decision. *)
90
91 (** {2 Callback Type} *)
92
93 type callback = input -> output
94 (** Callback function type for PreToolUse hooks. *)
95
96 (** {2 Conversion Functions} *)
97
98 val input_of_proto : Proto.Hooks.PreToolUse.Input.t -> input
99 (** [input_of_proto proto] converts wire format input to typed input. *)
100
101 val output_to_proto : output -> Proto.Hooks.PreToolUse.Output.t
102 (** [output_to_proto output] converts typed output to wire format. *)
103end
104
105(** PostToolUse hook - fires after tool execution *)
106module PostToolUse : sig
107 (** {2 Input} *)
108
109 type input = {
110 session_id : string;
111 transcript_path : string;
112 tool_name : string;
113 tool_input : Tool_input.t;
114 tool_response : Jsont.json; (* Response varies by tool *)
115 }
116 (** Input provided to PostToolUse hooks. Note: [tool_response] remains as
117 {!type:Jsont.json} since response schemas vary by tool. *)
118
119 (** {2 Output} *)
120
121 type output = {
122 block : bool;
123 reason : string option;
124 additional_context : string option;
125 }
126 (** Output from PostToolUse hooks. *)
127
128 (** {2 Response Builders} *)
129
130 val continue : ?additional_context:string -> unit -> output
131 (** [continue ?additional_context ()] creates a continue response.
132 @param additional_context Optional context to add to the transcript *)
133
134 val block : ?reason:string -> ?additional_context:string -> unit -> output
135 (** [block ?reason ?additional_context ()] creates a block response.
136 @param reason Optional explanation for blocking
137 @param additional_context Optional context to add to the transcript *)
138
139 (** {2 Callback Type} *)
140
141 type callback = input -> output
142 (** Callback function type for PostToolUse hooks. *)
143
144 (** {2 Conversion Functions} *)
145
146 val input_of_proto : Proto.Hooks.PostToolUse.Input.t -> input
147 (** [input_of_proto proto] converts wire format input to typed input. *)
148
149 val output_to_proto : output -> Proto.Hooks.PostToolUse.Output.t
150 (** [output_to_proto output] converts typed output to wire format. *)
151end
152
153(** UserPromptSubmit hook - fires when user submits a prompt *)
154module UserPromptSubmit : sig
155 (** {2 Input} *)
156
157 type input = {
158 session_id : string;
159 transcript_path : string;
160 prompt : string;
161 }
162 (** Input provided to UserPromptSubmit hooks. *)
163
164 (** {2 Output} *)
165
166 type output = {
167 block : bool;
168 reason : string option;
169 additional_context : string option;
170 }
171 (** Output from UserPromptSubmit hooks. *)
172
173 (** {2 Response Builders} *)
174
175 val continue : ?additional_context:string -> unit -> output
176 (** [continue ?additional_context ()] creates a continue response.
177 @param additional_context Optional context to add to the transcript *)
178
179 val block : ?reason:string -> unit -> output
180 (** [block ?reason ()] creates a block response.
181 @param reason Optional explanation for blocking *)
182
183 (** {2 Callback Type} *)
184
185 type callback = input -> output
186 (** Callback function type for UserPromptSubmit hooks. *)
187
188 (** {2 Conversion Functions} *)
189
190 val input_of_proto : Proto.Hooks.UserPromptSubmit.Input.t -> input
191 (** [input_of_proto proto] converts wire format input to typed input. *)
192
193 val output_to_proto : output -> Proto.Hooks.UserPromptSubmit.Output.t
194 (** [output_to_proto output] converts typed output to wire format. *)
195end
196
197(** Stop hook - fires when conversation stops *)
198module Stop : sig
199 (** {2 Input} *)
200
201 type input = {
202 session_id : string;
203 transcript_path : string;
204 stop_hook_active : bool;
205 }
206 (** Input provided to Stop hooks. *)
207
208 (** {2 Output} *)
209
210 type output = { block : bool; reason : string option }
211 (** Output from Stop hooks. *)
212
213 (** {2 Response Builders} *)
214
215 val continue : unit -> output
216 (** [continue ()] creates a continue response. *)
217
218 val block : ?reason:string -> unit -> output
219 (** [block ?reason ()] creates a block response.
220 @param reason Optional explanation for blocking *)
221
222 (** {2 Callback Type} *)
223
224 type callback = input -> output
225 (** Callback function type for Stop hooks. *)
226
227 (** {2 Conversion Functions} *)
228
229 val input_of_proto : Proto.Hooks.Stop.Input.t -> input
230 (** [input_of_proto proto] converts wire format input to typed input. *)
231
232 val output_to_proto : output -> Proto.Hooks.Stop.Output.t
233 (** [output_to_proto output] converts typed output to wire format. *)
234end
235
236(** SubagentStop hook - fires when a subagent stops *)
237module SubagentStop : sig
238 (** {2 Input} *)
239
240 type input = Stop.input
241 (** Same structure as Stop.input *)
242
243 (** {2 Output} *)
244
245 type output = Stop.output
246 (** Same structure as Stop.output *)
247
248 (** {2 Response Builders} *)
249
250 val continue : unit -> output
251 (** [continue ()] creates a continue response. *)
252
253 val block : ?reason:string -> unit -> output
254 (** [block ?reason ()] creates a block response.
255 @param reason Optional explanation for blocking *)
256
257 (** {2 Callback Type} *)
258
259 type callback = input -> output
260 (** Callback function type for SubagentStop hooks. *)
261
262 (** {2 Conversion Functions} *)
263
264 val input_of_proto : Proto.Hooks.SubagentStop.Input.t -> input
265 (** [input_of_proto proto] converts wire format input to typed input. *)
266
267 val output_to_proto : output -> Proto.Hooks.SubagentStop.Output.t
268 (** [output_to_proto output] converts typed output to wire format. *)
269end
270
271(** PreCompact hook - fires before message compaction *)
272module PreCompact : sig
273 (** {2 Input} *)
274
275 type input = { session_id : string; transcript_path : string }
276 (** Input provided to PreCompact hooks. *)
277
278 (** {2 Callback Type} *)
279
280 type callback = input -> unit
281 (** Callback function type for PreCompact hooks. PreCompact hooks have no
282 output - they are notification-only. *)
283
284 (** {2 Conversion Functions} *)
285
286 val input_of_proto : Proto.Hooks.PreCompact.Input.t -> input
287 (** [input_of_proto proto] converts wire format input to typed input. *)
288end
289
290(** {1 Hook Configuration} *)
291
292type t
293(** Hook configuration.
294
295 Hooks are configured using a builder pattern:
296 {[
297 Hooks.empty
298 |> Hooks.on_pre_tool_use ~pattern:"Bash" bash_handler
299 |> Hooks.on_post_tool_use post_handler
300 ]} *)
301
302val empty : t
303(** [empty] is an empty hook configuration with no callbacks. *)
304
305val on_pre_tool_use : ?pattern:string -> PreToolUse.callback -> t -> t
306(** [on_pre_tool_use ?pattern callback config] adds a PreToolUse hook.
307 @param pattern
308 Optional regex pattern to match tool names (e.g., "Bash|Edit")
309 @param callback Function to invoke on matching events *)
310
311val on_post_tool_use : ?pattern:string -> PostToolUse.callback -> t -> t
312(** [on_post_tool_use ?pattern callback config] adds a PostToolUse hook.
313 @param pattern Optional regex pattern to match tool names
314 @param callback Function to invoke on matching events *)
315
316val on_user_prompt_submit : UserPromptSubmit.callback -> t -> t
317(** [on_user_prompt_submit callback config] adds a UserPromptSubmit hook.
318 @param callback Function to invoke on prompt submission *)
319
320val on_stop : Stop.callback -> t -> t
321(** [on_stop callback config] adds a Stop hook.
322 @param callback Function to invoke on conversation stop *)
323
324val on_subagent_stop : SubagentStop.callback -> t -> t
325(** [on_subagent_stop callback config] adds a SubagentStop hook.
326 @param callback Function to invoke on subagent stop *)
327
328val on_pre_compact : PreCompact.callback -> t -> t
329(** [on_pre_compact callback config] adds a PreCompact hook.
330 @param callback Function to invoke before message compaction *)
331
332(** {1 Internal - for client use} *)
333
334val get_callbacks :
335 t ->
336 (Proto.Hooks.event
337 * (string option * (Jsont.json -> Proto.Hooks.result)) list)
338 list
339(** [get_callbacks config] returns hook configuration in format suitable for
340 registration with the CLI.
341
342 This function converts typed callbacks into wire format handlers that:
343 - Parse JSON input using Proto.Hooks types
344 - Convert to typed input using input_of_proto
345 - Invoke the user's typed callback
346 - Convert output back to wire format using output_to_proto
347
348 This is an internal function used by {!Client} - you should not need to call
349 it directly. *)