OCaml Claude SDK using Eio and Jsont
at main 349 lines 11 kB view raw
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. *)