My aggregated monorepo of OCaml code, automaintained
at doc-fixes 314 lines 12 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** Client interface for interacting with Claude. 7 8 This module provides the high-level client API for sending messages to 9 Claude and receiving responses. It handles the bidirectional streaming 10 protocol, permission callbacks, and hooks. 11 12 {2 Basic Usage} 13 14 {[ 15 Eio.Switch.run @@ fun sw -> 16 let client = Client.create ~sw ~process_mgr ~clock () in 17 Client.query client "What is 2+2?"; 18 19 let messages = Client.receive_all client in 20 List.iter 21 (function 22 | Message.Assistant msg -> 23 Printf.printf "Claude: %s\n" (Message.Assistant.text msg) 24 | _ -> ()) 25 messages 26 ]} 27 28 {2 Features} 29 30 - {b Message Streaming}: Messages are streamed lazily via {!Seq.t} 31 - {b Permission Control}: Custom permission callbacks for tool usage 32 - {b Hooks}: Intercept and modify tool execution 33 - {b Dynamic Control}: Change settings mid-conversation 34 - {b Resource Management}: Automatic cleanup via Eio switches 35 36 {2 Message Flow} 37 38 1. Create a client with {!create} 2. Send messages with {!query} or 39 {!Advanced.send_message} 3. Receive responses with {!receive} or {!receive_all} 4. 40 Continue multi-turn conversations by sending more messages 5. Client 41 automatically cleans up when the switch exits 42 43 {2 Advanced Features} 44 45 - Permission discovery mode for understanding required permissions 46 - Mid-conversation model switching and permission mode changes 47 - Server capability introspection *) 48 49val src : Logs.Src.t 50(** The log source for client operations *) 51 52type t 53(** The type of Claude clients. *) 54 55val session_id : t -> string option 56(** [session_id t] returns the session ID if one has been received from Claude. 57 The session ID is provided in system init messages and uniquely identifies 58 the current conversation session. *) 59 60val create : 61 ?options:Options.t -> 62 sw:Eio.Switch.t -> 63 process_mgr:_ Eio.Process.mgr -> 64 clock:float Eio.Time.clock_ty Eio.Resource.t -> 65 unit -> 66 t 67(** [create ?options ~sw ~process_mgr ~clock ()] creates a new Claude client. 68 69 @param options Configuration options (defaults to {!Options.default}) 70 @param sw Eio switch for resource management 71 @param process_mgr Eio process manager for spawning the Claude CLI 72 @param clock Eio clock for time operations *) 73 74(** {1 Simple Query Interface} *) 75 76val query : t -> string -> unit 77(** [query t prompt] sends a text message to Claude. 78 79 This is a convenience function for simple string messages. For more complex 80 messages with tool results or multiple content blocks, use 81 {!Advanced.send_message} instead. *) 82 83val respond_to_tool : 84 t -> tool_use_id:string -> content:Jsont.json -> ?is_error:bool -> unit -> unit 85(** [respond_to_tool t ~tool_use_id ~content ?is_error ()] responds to a tool 86 use request. 87 88 {b Duplicate protection:} If the same [tool_use_id] has already been 89 responded to, this call is silently skipped with a warning log. This 90 prevents API errors from duplicate tool responses. 91 92 @param tool_use_id The ID from the {!Response.Tool_use.t} event 93 @param content The result content (can be a string or array of content blocks) 94 @param is_error Whether this is an error response (default: false) *) 95 96val respond_to_tools : t -> (string * Jsont.json * bool option) list -> unit 97(** [respond_to_tools t responses] responds to multiple tool use requests at 98 once. 99 100 {b Duplicate protection:} Any [tool_use_id] that has already been 101 responded to is filtered out with a warning log. 102 103 Each tuple is [(tool_use_id, content, is_error option)] where content 104 can be a string or array of content blocks. 105 106 Example: 107 {[ 108 Client.respond_to_tools client 109 [ 110 ("tool_use_123", Jsont.string "Success", None); 111 ("tool_use_456", Jsont.string "Error occurred", Some true); 112 ] 113 ]} *) 114 115val clear_tool_response_tracking : t -> unit 116(** [clear_tool_response_tracking t] clears the internal tracking of which 117 tool_use_ids have been responded to. 118 119 This is useful when starting a new conversation or turn where you want 120 to allow responses to previously-seen tool IDs. Normally this is not 121 needed as tool IDs are unique per conversation turn. *) 122 123(** {1 Response Handling} *) 124 125val run : t -> handler:#Handler.handler -> unit 126(** [run t ~handler] processes all responses using the given handler. 127 128 This is the recommended way to handle responses in an event-driven style. 129 The handler's methods will be called for each response event as it arrives. 130 131 Example: 132 {[ 133 let my_handler = object 134 inherit Claude.Handler.default 135 method! on_text t = print_endline (Response.Text.content t) 136 method! on_complete c = 137 Printf.printf "Cost: $%.4f\n" 138 (Option.value ~default:0.0 (Response.Complete.total_cost_usd c)) 139 end in 140 Client.query client "Hello"; 141 Client.run client ~handler:my_handler 142 ]} *) 143 144val receive : t -> Response.t Seq.t 145(** [receive t] returns a lazy sequence of responses from Claude. 146 147 The sequence yields response events as they arrive from Claude, including: 148 - {!constructor:Response.Text} - Text content from assistant 149 - {!constructor:Response.Tool_use} - Tool invocation requests 150 - {!constructor:Response.Thinking} - Internal reasoning 151 - {!constructor:Response.Init} - Session initialization 152 - {!constructor:Response.Error} - Error events 153 - {!constructor:Response.Complete} - Final result with usage statistics 154 155 Control messages (permission requests, hook callbacks) are handled 156 internally and not yielded to the sequence. 157 158 For simple cases, prefer {!run} with a handler instead. *) 159 160val receive_all : t -> Response.t list 161(** [receive_all t] collects all responses into a list. 162 163 This is a convenience function that consumes the {!receive} sequence. Use 164 this when you want to process all responses at once rather than streaming 165 them. 166 167 For most cases, prefer {!run} with a handler instead. *) 168 169val interrupt : t -> unit 170(** [interrupt t] sends an interrupt signal to stop Claude's execution. *) 171 172(** {1 Dynamic Control} 173 174 These methods allow you to change Claude's behavior mid-conversation without 175 recreating the client. This is useful for: 176 177 - Adjusting permission strictness based on user feedback 178 - Switching to faster/cheaper models for simple tasks 179 - Adapting to changing requirements during long conversations 180 - Introspecting server capabilities 181 182 {2 Example: Adaptive Permission Control} 183 184 {[ 185 (* Start with strict permissions *) 186 let client = Client.create ~sw ~process_mgr ~clock 187 ~options:(Options.default 188 |> Options.with_permission_mode Permissions.Mode.Default) () 189 in 190 191 Client.query client "Analyze this code"; 192 let _ = Client.receive_all client in 193 194 (* User approves, switch to auto-accept edits *) 195 Client.set_permission_mode client Permissions.Mode.Accept_edits; 196 197 Client.query client "Now refactor it"; 198 let _ = Client.receive_all client in 199 ]} 200 201 {2 Example: Model Switching for Efficiency} 202 203 {[ 204 (* Use powerful model for complex analysis *) 205 let client = Client.create ~sw ~process_mgr ~clock 206 ~options:(Options.default |> Options.with_model "claude-sonnet-4-5") () 207 in 208 209 Client.query client "Design a new architecture for this system"; 210 let _ = Client.receive_all client in 211 212 (* Switch to faster model for simple tasks *) 213 Client.set_model client "claude-haiku-4"; 214 215 Client.query client "Now write a README"; 216 let _ = Client.receive_all client in 217 ]} 218 219 {2 Example: Server Introspection} 220 221 {[ 222 let info = Client.get_server_info client in 223 Printf.printf "Claude CLI version: %s\n" 224 (Sdk_control.Server_info.version info); 225 Printf.printf "Capabilities: %s\n" 226 (String.concat ", " (Sdk_control.Server_info.capabilities info)) 227 ]} *) 228 229val set_permission_mode : t -> Permissions.Mode.t -> unit 230(** [set_permission_mode t mode] changes the permission mode mid-conversation. 231 232 This allows switching between permission modes without recreating the 233 client: 234 - {!Permissions.Mode.Default} - Prompt for all permissions 235 - {!Permissions.Mode.Accept_edits} - Auto-accept file edits 236 - {!Permissions.Mode.Plan} - Planning mode with restricted execution 237 - {!Permissions.Mode.Bypass_permissions} - Skip all permission checks 238 239 @raise Failure if the server returns an error *) 240 241val set_model : t -> Model.t -> unit 242(** [set_model t model] switches to a different AI model mid-conversation. 243 244 Common models: 245 - [`Sonnet_4_5] - Most capable, balanced performance 246 - [`Opus_4] - Maximum capability for complex tasks 247 - [`Haiku_4] - Fast and cost-effective 248 249 @raise Failure if the model is invalid or unavailable *) 250 251val get_server_info : t -> Server_info.t 252(** [get_server_info t] retrieves server capabilities and metadata. 253 254 Returns information about: 255 - Server version string 256 - Available capabilities 257 - Supported commands 258 - Available output styles 259 260 Useful for feature detection and debugging. 261 262 @raise Failure if the server returns an error *) 263 264(** {1 Permission Discovery} *) 265 266val enable_permission_discovery : t -> unit 267(** [enable_permission_discovery t] enables permission discovery mode. 268 269 In discovery mode, all tool usage is logged but allowed. Use 270 {!discovered_permissions} to retrieve the list of permissions that were 271 requested during execution. 272 273 This is useful for understanding what permissions your prompt requires. *) 274 275val discovered_permissions : t -> Permissions.Rule.t list 276(** [discovered_permissions t] returns permissions discovered during execution. 277 278 Only useful after enabling {!enable_permission_discovery}. *) 279 280(** {1 Advanced Interface} 281 282 Low-level access to the protocol for advanced use cases. *) 283 284module Advanced : sig 285 val send_message : t -> Message.t -> unit 286 (** [send_message t msg] sends a message to Claude. 287 288 Supports all message types including user messages with tool results. *) 289 290 val send_user_message : t -> Message.User.t -> unit 291 (** [send_user_message t msg] sends a user message to Claude. *) 292 293 val send_raw : t -> Sdk_control.t -> unit 294 (** [send_raw t control] sends a raw SDK control message. 295 296 This is for advanced use cases that need direct control protocol access. *) 297 298 val send_json : t -> Jsont.json -> unit 299 (** [send_json t json] sends raw JSON to Claude. 300 301 This is the lowest-level send operation. Use with caution. *) 302 303 val receive_raw : t -> Incoming.t Seq.t 304 (** [receive_raw t] returns a lazy sequence of raw incoming messages. 305 306 This includes all message types before Response conversion: 307 - {!Proto.Incoming.t.constructor-Message} - Regular messages 308 - {!Proto.Incoming.t.constructor-Control_response} - Control responses (normally handled 309 internally) 310 - {!Proto.Incoming.t.constructor-Control_request} - Control requests (normally handled 311 internally) 312 313 Most users should use {!receive} or {!run} instead. *) 314end