this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

claudeio

+957 -102
+1
claudeio/claude.opam
··· 10 10 "fmt" 11 11 "logs" 12 12 "ezjsonm" 13 + "jsont" {>= "0.2.0"} 13 14 "alcotest" {with-test} 14 15 "odoc" {with-doc} 15 16 ]
+1
claudeio/dune-project
··· 13 13 fmt 14 14 logs 15 15 ezjsonm 16 + (jsont (>= 0.2.0)) 16 17 (alcotest :with-test)))
+3
claudeio/lib/claude.ml
··· 1 + module Model = Model 1 2 module Content_block = Content_block 2 3 module Message = Message 3 4 module Control = Control 4 5 module Permissions = Permissions 5 6 module Hooks = Hooks 7 + module Sdk_control = Sdk_control 8 + module Structured_output = Structured_output 6 9 module Options = Options 7 10 module Transport = Transport 8 11 module Client = Client
+9
claudeio/lib/claude.mli
··· 153 153 module Options = Options 154 154 (** Configuration options for Claude sessions. *) 155 155 156 + module Model = Model 157 + (** Claude AI model identifiers with type-safe variants. *) 158 + 156 159 module Content_block = Content_block 157 160 (** Content blocks for messages (text, tool use, tool results, thinking). *) 158 161 ··· 167 170 168 171 module Hooks = Hooks 169 172 (** Hooks system for event interception. *) 173 + 174 + module Sdk_control = Sdk_control 175 + (** SDK control protocol for dynamic configuration. *) 176 + 177 + module Structured_output = Structured_output 178 + (** Structured output support using JSON Schema. *) 170 179 171 180 module Transport = Transport 172 181 (** Low-level transport layer for CLI communication. *)
+80 -1
claudeio/lib/client.ml
··· 8 8 hook_callbacks : (string, Hooks.callback) Hashtbl.t; 9 9 mutable next_callback_id : int; 10 10 mutable session_id : string option; 11 + control_responses : (string, Ezjsonm.value) Hashtbl.t; 12 + control_mutex : Eio.Mutex.t; 13 + control_condition : Eio.Condition.t; 11 14 } 12 15 13 16 let handle_control_request t control_msg = ··· 166 169 (* Handle control responses (e.g., initialize response) *) 167 170 let request_id = Json_utils.find_string json ["response"; "request_id"] in 168 171 Log.debug (fun m -> m "Received control response for request_id: %s" request_id); 169 - (* Don't yield control responses as messages, just loop *) 172 + (* Store the response and signal waiting threads *) 173 + Eio.Mutex.use_rw ~protect:false t.control_mutex (fun () -> 174 + Hashtbl.replace t.control_responses request_id json; 175 + Eio.Condition.broadcast t.control_condition 176 + ); 170 177 loop () 171 178 172 179 | _ -> ··· 217 224 hook_callbacks; 218 225 next_callback_id = 0; 219 226 session_id = None; 227 + control_responses = Hashtbl.create 16; 228 + control_mutex = Eio.Mutex.create (); 229 + control_condition = Eio.Condition.create (); 220 230 } in 221 231 222 232 (* Register hooks and send initialize if hooks are configured *) ··· 310 320 311 321 let with_permission_callback t callback = 312 322 { t with permission_callback = Some callback } 323 + 324 + (* Helper to send a control request and wait for response *) 325 + let send_control_request t ~request_id request = 326 + let open Ezjsonm in 327 + (* Send the control request *) 328 + let control_msg = Sdk_control.create_request ~request_id ~request in 329 + let json = Sdk_control.to_json control_msg in 330 + Log.info (fun m -> m "Sending control request: %s" (value_to_string json)); 331 + Transport.send t.transport json; 332 + 333 + (* Wait for the response with timeout *) 334 + let max_wait = 10.0 in (* 10 seconds timeout *) 335 + let start_time = Unix.gettimeofday () in 336 + 337 + let rec wait_for_response () = 338 + Eio.Mutex.use_rw ~protect:false t.control_mutex (fun () -> 339 + match Hashtbl.find_opt t.control_responses request_id with 340 + | Some response_json -> 341 + (* Remove it from the table *) 342 + Hashtbl.remove t.control_responses request_id; 343 + response_json 344 + | None -> 345 + let elapsed = Unix.gettimeofday () -. start_time in 346 + if elapsed > max_wait then 347 + raise (Failure (Printf.sprintf "Timeout waiting for control response: %s" request_id)) 348 + else ( 349 + (* Release mutex and wait for signal *) 350 + Eio.Condition.await_no_mutex t.control_condition; 351 + wait_for_response () 352 + ) 353 + ) 354 + in 355 + 356 + let response_json = wait_for_response () in 357 + Log.debug (fun m -> m "Received control response: %s" (value_to_string response_json)); 358 + 359 + (* Parse the response *) 360 + let response = find response_json ["response"] |> Sdk_control.Response.of_json in 361 + match response with 362 + | Sdk_control.Response.Success s -> s.response 363 + | Sdk_control.Response.Error e -> 364 + raise (Failure (Printf.sprintf "Control request failed: %s" e.error)) 365 + 366 + let set_permission_mode t mode = 367 + let request_id = Printf.sprintf "set_perm_mode_%f" (Unix.gettimeofday ()) in 368 + let request = Sdk_control.Request.set_permission_mode ~mode in 369 + let _response = send_control_request t ~request_id request in 370 + Log.info (fun m -> m "Permission mode set to: %a" Permissions.Mode.pp mode) 371 + 372 + let set_model t model = 373 + let model_str = Model.to_string model in 374 + let request_id = Printf.sprintf "set_model_%f" (Unix.gettimeofday ()) in 375 + let request = Sdk_control.Request.set_model ~model:model_str in 376 + let _response = send_control_request t ~request_id request in 377 + Log.info (fun m -> m "Model set to: %a" Model.pp model) 378 + 379 + let set_model_string t model_str = 380 + set_model t (Model.of_string model_str) 381 + 382 + let get_server_info t = 383 + let request_id = Printf.sprintf "get_server_info_%f" (Unix.gettimeofday ()) in 384 + let request = Sdk_control.Request.get_server_info () in 385 + match send_control_request t ~request_id request with 386 + | Some response_data -> 387 + let server_info = Sdk_control.Server_info.of_json response_data in 388 + Log.info (fun m -> m "Retrieved server info: %a" Sdk_control.Server_info.pp server_info); 389 + server_info 390 + | None -> 391 + raise (Failure "No response data from get_server_info request")
+193 -3
claudeio/lib/client.mli
··· 1 + (** Client interface for interacting with Claude. 2 + 3 + This module provides the high-level client API for sending messages to 4 + Claude and receiving responses. It handles the bidirectional streaming 5 + protocol, permission callbacks, and hooks. 6 + 7 + {2 Basic Usage} 8 + 9 + {[ 10 + Eio.Switch.run @@ fun sw -> 11 + let client = Client.create ~sw ~process_mgr () in 12 + Client.query client "What is 2+2?"; 13 + 14 + let messages = Client.receive_all client in 15 + List.iter (function 16 + | Message.Assistant msg -> 17 + Printf.printf "Claude: %s\n" (Message.Assistant.text msg) 18 + | _ -> () 19 + ) messages 20 + ]} 21 + 22 + {2 Features} 23 + 24 + - {b Message Streaming}: Messages are streamed lazily via {!Seq.t} 25 + - {b Permission Control}: Custom permission callbacks for tool usage 26 + - {b Hooks}: Intercept and modify tool execution 27 + - {b Dynamic Control}: Change settings mid-conversation 28 + - {b Resource Management}: Automatic cleanup via Eio switches 29 + 30 + {2 Message Flow} 31 + 32 + 1. Create a client with {!create} 33 + 2. Send messages with {!query} or {!send_message} 34 + 3. Receive responses with {!receive} or {!receive_all} 35 + 4. Continue multi-turn conversations by sending more messages 36 + 5. Client automatically cleans up when the switch exits 37 + 38 + {2 Advanced Features} 39 + 40 + - Permission discovery mode for understanding required permissions 41 + - Mid-conversation model switching and permission mode changes 42 + - Server capability introspection *) 43 + 1 44 (** The log source for client operations *) 2 45 val src : Logs.Src.t 3 46 4 47 type t 48 + (** The type of Claude clients. *) 5 49 6 - val create : 7 - ?options:Options.t -> 8 - sw:Eio.Switch.t -> 50 + val create : 51 + ?options:Options.t -> 52 + sw:Eio.Switch.t -> 9 53 process_mgr:_ Eio.Process.mgr -> 10 54 unit -> t 55 + (** [create ?options ~sw ~process_mgr ()] creates a new Claude client. 56 + 57 + @param options Configuration options (defaults to {!Options.default}) 58 + @param sw Eio switch for resource management 59 + @param process_mgr Eio process manager for spawning the Claude CLI *) 11 60 12 61 val query : t -> string -> unit 62 + (** [query t prompt] sends a text message to Claude. 63 + 64 + This is a convenience function for simple string messages. For more 65 + complex messages with tool results or multiple content blocks, use 66 + {!send_message} instead. *) 67 + 13 68 val send_message : t -> Message.t -> unit 69 + (** [send_message t msg] sends a message to Claude. 70 + 71 + Supports all message types including user messages with tool results. *) 72 + 14 73 val send_user_message : t -> Message.User.t -> unit 74 + (** [send_user_message t msg] sends a user message to Claude. *) 15 75 16 76 val receive : t -> Message.t Seq.t 77 + (** [receive t] returns a lazy sequence of messages from Claude. 78 + 79 + The sequence yields messages as they arrive from Claude, including: 80 + - {!Message.Assistant} - Claude's responses 81 + - {!Message.System} - System notifications 82 + - {!Message.Result} - Final result with usage statistics 83 + 84 + Control messages (permission requests, hook callbacks) are handled 85 + internally and not yielded to the sequence. *) 86 + 17 87 val receive_all : t -> Message.t list 88 + (** [receive_all t] collects all messages into a list. 89 + 90 + This is a convenience function that consumes the {!receive} sequence. 91 + Use this when you want to process all messages at once rather than 92 + streaming them. *) 18 93 19 94 val interrupt : t -> unit 95 + (** [interrupt t] sends an interrupt signal to stop Claude's execution. *) 20 96 21 97 val discover_permissions : t -> t 98 + (** [discover_permissions t] enables permission discovery mode. 99 + 100 + In discovery mode, all tool usage is logged but allowed. Use 101 + {!get_discovered_permissions} to retrieve the list of permissions 102 + that were requested during execution. 103 + 104 + This is useful for understanding what permissions your prompt requires. *) 105 + 22 106 val get_discovered_permissions : t -> Permissions.Rule.t list 107 + (** [get_discovered_permissions t] returns permissions discovered during execution. 108 + 109 + Only useful after enabling {!discover_permissions}. *) 110 + 23 111 val with_permission_callback : t -> Permissions.callback -> t 112 + (** [with_permission_callback t callback] updates the permission callback. 113 + 114 + Allows dynamically changing the permission callback without recreating 115 + the client. *) 116 + 117 + (** {1 Dynamic Control Methods} 118 + 119 + These methods allow you to change Claude's behavior mid-conversation 120 + without recreating the client. This is useful for: 121 + 122 + - Adjusting permission strictness based on user feedback 123 + - Switching to faster/cheaper models for simple tasks 124 + - Adapting to changing requirements during long conversations 125 + - Introspecting server capabilities 126 + 127 + {2 Example: Adaptive Permission Control} 128 + 129 + {[ 130 + (* Start with strict permissions *) 131 + let client = Client.create ~sw ~process_mgr 132 + ~options:(Options.default 133 + |> Options.with_permission_mode Permissions.Mode.Default) () 134 + in 135 + 136 + Client.query client "Analyze this code"; 137 + let _ = Client.receive_all client in 138 + 139 + (* User approves, switch to auto-accept edits *) 140 + Client.set_permission_mode client Permissions.Mode.Accept_edits; 141 + 142 + Client.query client "Now refactor it"; 143 + let _ = Client.receive_all client in 144 + ]} 145 + 146 + {2 Example: Model Switching for Efficiency} 147 + 148 + {[ 149 + (* Use powerful model for complex analysis *) 150 + let client = Client.create ~sw ~process_mgr 151 + ~options:(Options.default |> Options.with_model "claude-sonnet-4-5") () 152 + in 153 + 154 + Client.query client "Design a new architecture for this system"; 155 + let _ = Client.receive_all client in 156 + 157 + (* Switch to faster model for simple tasks *) 158 + Client.set_model client "claude-haiku-4"; 159 + 160 + Client.query client "Now write a README"; 161 + let _ = Client.receive_all client in 162 + ]} 163 + 164 + {2 Example: Server Introspection} 165 + 166 + {[ 167 + let info = Client.get_server_info client in 168 + Printf.printf "Claude CLI version: %s\n" 169 + (Sdk_control.Server_info.version info); 170 + Printf.printf "Capabilities: %s\n" 171 + (String.concat ", " (Sdk_control.Server_info.capabilities info)); 172 + ]} *) 173 + 174 + val set_permission_mode : t -> Permissions.Mode.t -> unit 175 + (** [set_permission_mode t mode] changes the permission mode mid-conversation. 176 + 177 + This allows switching between permission modes without recreating the client: 178 + - {!Permissions.Mode.Default} - Prompt for all permissions 179 + - {!Permissions.Mode.Accept_edits} - Auto-accept file edits 180 + - {!Permissions.Mode.Plan} - Planning mode with restricted execution 181 + - {!Permissions.Mode.Bypass_permissions} - Skip all permission checks 182 + 183 + @raise Failure if the server returns an error *) 184 + 185 + val set_model : t -> Model.t -> unit 186 + (** [set_model t model] switches to a different AI model mid-conversation. 187 + 188 + Common models: 189 + - [`Sonnet_4_5] - Most capable, balanced performance 190 + - [`Opus_4] - Maximum capability for complex tasks 191 + - [`Haiku_4] - Fast and cost-effective 192 + 193 + @raise Failure if the model is invalid or unavailable *) 194 + 195 + val set_model_string : t -> string -> unit 196 + (** [set_model_string t model] switches to a different AI model using a string. 197 + 198 + This is a convenience function that parses the string using {!Model.of_string}. 199 + 200 + @raise Failure if the model is invalid or unavailable *) 201 + 202 + val get_server_info : t -> Sdk_control.Server_info.t 203 + (** [get_server_info t] retrieves server capabilities and metadata. 204 + 205 + Returns information about: 206 + - Server version string 207 + - Available capabilities 208 + - Supported commands 209 + - Available output styles 210 + 211 + Useful for feature detection and debugging. 212 + 213 + @raise Failure if the server returns an error *)
+1 -1
claudeio/lib/dune
··· 1 1 (library 2 2 (public_name claude) 3 3 (name claude) 4 - (libraries eio eio.unix ezjsonm fmt logs)) 4 + (libraries eio eio.unix ezjsonm fmt logs jsont))
+65 -21
claudeio/lib/message.ml
··· 98 98 end 99 99 100 100 module Assistant = struct 101 + type error = [ 102 + | `Authentication_failed 103 + | `Billing_error 104 + | `Rate_limit 105 + | `Invalid_request 106 + | `Server_error 107 + | `Unknown 108 + ] 109 + 110 + let error_to_string = function 111 + | `Authentication_failed -> "authentication_failed" 112 + | `Billing_error -> "billing_error" 113 + | `Rate_limit -> "rate_limit" 114 + | `Invalid_request -> "invalid_request" 115 + | `Server_error -> "server_error" 116 + | `Unknown -> "unknown" 117 + 118 + let error_of_string = function 119 + | "authentication_failed" -> `Authentication_failed 120 + | "billing_error" -> `Billing_error 121 + | "rate_limit" -> `Rate_limit 122 + | "invalid_request" -> `Invalid_request 123 + | "server_error" -> `Server_error 124 + | "unknown" | _ -> `Unknown 125 + 101 126 type t = { 102 127 content : Content_block.t list; 103 128 model : string; 129 + error : error option; 104 130 } 105 - 106 - let create ~content ~model = { content; model } 131 + 132 + let create ~content ~model ?error () = { content; model; error } 107 133 let content t = t.content 108 134 let model t = t.model 135 + let error t = t.error 109 136 110 137 let get_text_blocks t = 111 138 List.filter_map (function ··· 135 162 String.concat "\n" (get_text_blocks t) 136 163 137 164 let to_json t = 165 + let msg_fields = [ 166 + ("content", `A (List.map Content_block.to_json t.content)); 167 + ("model", `String t.model); 168 + ] in 169 + let msg_fields = match t.error with 170 + | Some err -> ("error", `String (error_to_string err)) :: msg_fields 171 + | None -> msg_fields 172 + in 138 173 `O [ 139 174 ("type", `String "assistant"); 140 - ("message", `O [ 141 - ("content", `A (List.map Content_block.to_json t.content)); 142 - ("model", `String t.model); 143 - ]); 175 + ("message", `O msg_fields); 144 176 ] 145 177 146 178 let of_json = function 147 179 | `O fields -> 148 180 let message = List.assoc "message" fields in 149 - let content, model = match message with 181 + let content, model, error = match message with 150 182 | `O msg_fields -> 151 - let content = 183 + let content = 152 184 match List.assoc "content" msg_fields with 153 185 | `A blocks -> List.map Content_block.of_json blocks 154 186 | _ -> raise (Invalid_argument "Assistant.of_json: invalid content") 155 187 in 156 188 let model = JU.assoc_string "model" msg_fields in 157 - content, model 189 + let error = 190 + match JU.assoc_string_opt "error" msg_fields with 191 + | Some err_str -> Some (error_of_string err_str) 192 + | None -> None 193 + in 194 + content, model, error 158 195 | _ -> raise (Invalid_argument "Assistant.of_json: invalid message") 159 196 in 160 - { content; model } 197 + { content; model; error } 161 198 | _ -> raise (Invalid_argument "Assistant.of_json: expected object") 162 199 163 200 let pp fmt t = ··· 341 378 total_cost_usd : float option; 342 379 usage : Usage.t option; 343 380 result : string option; 381 + structured_output : value option; 344 382 } 345 383 346 - let create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns 347 - ~session_id ?total_cost_usd ?usage ?result () = 384 + let create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns 385 + ~session_id ?total_cost_usd ?usage ?result ?structured_output () = 348 386 { subtype; duration_ms; duration_api_ms; is_error; num_turns; 349 - session_id; total_cost_usd; usage; result } 387 + session_id; total_cost_usd; usage; result; structured_output } 350 388 351 389 let subtype t = t.subtype 352 390 let duration_ms t = t.duration_ms ··· 357 395 let total_cost_usd t = t.total_cost_usd 358 396 let usage t = t.usage 359 397 let result t = t.result 398 + let structured_output t = t.structured_output 360 399 361 400 let to_json t = 362 401 let fields = [ ··· 380 419 | Some result -> ("result", `String result) :: fields 381 420 | None -> fields 382 421 in 422 + let fields = match t.structured_output with 423 + | Some output -> ("structured_output", output) :: fields 424 + | None -> fields 425 + in 383 426 `O fields 384 427 385 428 let of_json = function ··· 393 436 let total_cost_usd = JU.assoc_float_opt "total_cost_usd" fields in 394 437 let usage = Option.map Usage.of_json (List.assoc_opt "usage" fields) in 395 438 let result = JU.assoc_string_opt "result" fields in 439 + let structured_output = List.assoc_opt "structured_output" fields in 396 440 { subtype; duration_ms; duration_api_ms; is_error; num_turns; 397 - session_id; total_cost_usd; usage; result } 441 + session_id; total_cost_usd; usage; result; structured_output } 398 442 | _ -> raise (Invalid_argument "Result.of_json: expected object") 399 443 400 444 let pp fmt t = ··· 434 478 let user_with_tool_result ~tool_use_id ~content ?is_error () = 435 479 User (User.create_with_tool_result ~tool_use_id ~content ?is_error ()) 436 480 437 - let assistant ~content ~model = Assistant (Assistant.create ~content ~model) 438 - let assistant_text ~text ~model = 439 - Assistant (Assistant.create ~content:[Content_block.text text] ~model) 481 + let assistant ~content ~model ?error () = Assistant (Assistant.create ~content ~model ?error ()) 482 + let assistant_text ~text ~model ?error () = 483 + Assistant (Assistant.create ~content:[Content_block.text text] ~model ?error ()) 440 484 441 485 let system ~subtype ~data = System (System.create ~subtype ~data) 442 486 let system_init ~session_id = ··· 446 490 let data = System.Data.of_assoc [("error", `String error)] in 447 491 System (System.create ~subtype:"error" ~data) 448 492 449 - let result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns 450 - ~session_id ?total_cost_usd ?usage ?result () = 451 - Result (Result.create ~subtype ~duration_ms ~duration_api_ms ~is_error 452 - ~num_turns ~session_id ?total_cost_usd ?usage ?result ()) 493 + let result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns 494 + ~session_id ?total_cost_usd ?usage ?result ?structured_output () = 495 + Result (Result.create ~subtype ~duration_ms ~duration_api_ms ~is_error 496 + ~num_turns ~session_id ?total_cost_usd ?usage ?result ?structured_output ()) 453 497 454 498 let to_json = function 455 499 | User t -> User.to_json t
+59 -33
claudeio/lib/message.mli
··· 62 62 63 63 module Assistant : sig 64 64 (** Messages from Claude assistant. *) 65 - 65 + 66 + type error = [ 67 + | `Authentication_failed (** Authentication with Claude API failed *) 68 + | `Billing_error (** Billing or account issue *) 69 + | `Rate_limit (** Rate limit exceeded *) 70 + | `Invalid_request (** Request was invalid *) 71 + | `Server_error (** Internal server error *) 72 + | `Unknown (** Unknown error type *) 73 + ] 74 + (** The type of assistant message errors based on Python SDK error types. *) 75 + 76 + val error_to_string : error -> string 77 + (** [error_to_string err] converts an error to its string representation. *) 78 + 79 + val error_of_string : string -> error 80 + (** [error_of_string s] parses an error string. Unknown strings become [`Unknown]. *) 81 + 66 82 type t 67 83 (** The type of assistant messages. *) 68 - 69 - val create : content:Content_block.t list -> model:string -> t 70 - (** [create ~content ~model] creates an assistant message. 84 + 85 + val create : content:Content_block.t list -> model:string -> ?error:error -> unit -> t 86 + (** [create ~content ~model ?error ()] creates an assistant message. 71 87 @param content List of content blocks in the response 72 - @param model The model identifier used for the response *) 73 - 88 + @param model The model identifier used for the response 89 + @param error Optional error that occurred during message generation *) 90 + 74 91 val content : t -> Content_block.t list 75 92 (** [content t] returns the content blocks of the assistant message. *) 76 - 93 + 77 94 val model : t -> string 78 95 (** [model t] returns the model identifier. *) 96 + 97 + val error : t -> error option 98 + (** [error t] returns the optional error that occurred during message generation. *) 79 99 80 100 val get_text_blocks : t -> string list 81 101 (** [get_text_blocks t] extracts all text content from the message. *) ··· 230 250 type t 231 251 (** The type of result messages. *) 232 252 233 - val create : 234 - subtype:string -> 235 - duration_ms:int -> 236 - duration_api_ms:int -> 237 - is_error:bool -> 238 - num_turns:int -> 239 - session_id:string -> 240 - ?total_cost_usd:float -> 241 - ?usage:Usage.t -> 242 - ?result:string -> 253 + val create : 254 + subtype:string -> 255 + duration_ms:int -> 256 + duration_api_ms:int -> 257 + is_error:bool -> 258 + num_turns:int -> 259 + session_id:string -> 260 + ?total_cost_usd:float -> 261 + ?usage:Usage.t -> 262 + ?result:string -> 263 + ?structured_output:Ezjsonm.value -> 243 264 unit -> t 244 265 (** [create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns 245 266 ~session_id ?total_cost_usd ?usage ?result ()] creates a result message. ··· 251 272 @param session_id Unique session identifier 252 273 @param total_cost_usd Optional total cost in USD 253 274 @param usage Optional usage statistics as JSON 254 - @param result Optional result string *) 275 + @param result Optional result string 276 + @param structured_output Optional structured JSON output from Claude *) 255 277 256 278 val subtype : t -> string 257 279 (** [subtype t] returns the subtype of the result. *) ··· 279 301 280 302 val result : t -> string option 281 303 (** [result t] returns the optional result string. *) 282 - 304 + 305 + val structured_output : t -> Ezjsonm.value option 306 + (** [structured_output t] returns the optional structured JSON output. *) 307 + 283 308 val to_json : t -> Ezjsonm.value 284 309 (** [to_json t] converts the result message to its JSON representation. *) 285 310 ··· 310 335 (** [user_with_tool_result ~tool_use_id ~content ?is_error ()] creates a user message 311 336 containing a tool result. *) 312 337 313 - val assistant : content:Content_block.t list -> model:string -> t 314 - (** [assistant ~content ~model] creates an assistant message. *) 338 + val assistant : content:Content_block.t list -> model:string -> ?error:Assistant.error -> unit -> t 339 + (** [assistant ~content ~model ?error ()] creates an assistant message. *) 315 340 316 - val assistant_text : text:string -> model:string -> t 317 - (** [assistant_text ~text ~model] creates an assistant message with only text content. *) 341 + val assistant_text : text:string -> model:string -> ?error:Assistant.error -> unit -> t 342 + (** [assistant_text ~text ~model ?error ()] creates an assistant message with only text content. *) 318 343 319 344 val system : subtype:string -> data:System.Data.t -> t 320 345 (** [system ~subtype ~data] creates a system message. *) ··· 325 350 val system_error : error:string -> t 326 351 (** [system_error ~error] creates a system error message. *) 327 352 328 - val result : 329 - subtype:string -> 330 - duration_ms:int -> 331 - duration_api_ms:int -> 332 - is_error:bool -> 333 - num_turns:int -> 334 - session_id:string -> 335 - ?total_cost_usd:float -> 336 - ?usage:Result.Usage.t -> 337 - ?result:string -> 353 + val result : 354 + subtype:string -> 355 + duration_ms:int -> 356 + duration_api_ms:int -> 357 + is_error:bool -> 358 + num_turns:int -> 359 + session_id:string -> 360 + ?total_cost_usd:float -> 361 + ?usage:Result.Usage.t -> 362 + ?result:string -> 363 + ?structured_output:Ezjsonm.value -> 338 364 unit -> t 339 365 (** [result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns 340 366 ~session_id ?total_cost_usd ?usage ?result ()] creates a result message. *)
+50 -7
claudeio/lib/options.ml
··· 3 3 let src = Logs.Src.create "claude.options" ~doc:"Claude configuration options" 4 4 module Log = (val Logs.src_log src : Logs.LOG) 5 5 6 + type setting_source = User | Project | Local 7 + 6 8 type t = { 7 9 allowed_tools : string list; 8 10 disallowed_tools : string list; ··· 11 13 append_system_prompt : string option; 12 14 permission_mode : Permissions.Mode.t option; 13 15 permission_callback : Permissions.callback option; 14 - model : string option; 16 + model : Model.t option; 15 17 cwd : Eio.Fs.dir_ty Eio.Path.t option; 16 18 env : (string * string) list; 17 19 continue_conversation : bool; ··· 23 25 extra_args : (string * string option) list; 24 26 debug_stderr : Eio.Flow.sink_ty Eio.Flow.sink option; 25 27 hooks : Hooks.config option; 28 + max_budget_usd : float option; 29 + fallback_model : Model.t option; 30 + setting_sources : setting_source list option; 31 + max_buffer_size : int option; 32 + user : string option; 33 + output_format : Structured_output.t option; 26 34 } 27 35 28 36 let default = { ··· 45 53 extra_args = []; 46 54 debug_stderr = None; 47 55 hooks = None; 56 + max_budget_usd = None; 57 + fallback_model = None; 58 + setting_sources = None; 59 + max_buffer_size = None; 60 + user = None; 61 + output_format = None; 48 62 } 49 63 50 64 let create ··· 67 81 ?(extra_args = []) 68 82 ?debug_stderr 69 83 ?hooks 84 + ?max_budget_usd 85 + ?fallback_model 86 + ?setting_sources 87 + ?max_buffer_size 88 + ?user 89 + ?output_format 70 90 () = 71 91 { allowed_tools; disallowed_tools; max_thinking_tokens; 72 92 system_prompt; append_system_prompt; permission_mode; 73 93 permission_callback; model; cwd; env; 74 94 continue_conversation; resume; max_turns; 75 95 permission_prompt_tool_name; settings; add_dirs; 76 - extra_args; debug_stderr; hooks } 96 + extra_args; debug_stderr; hooks; 97 + max_budget_usd; fallback_model; setting_sources; 98 + max_buffer_size; user; output_format } 77 99 78 100 let allowed_tools t = t.allowed_tools 79 101 let disallowed_tools t = t.disallowed_tools ··· 94 116 let extra_args t = t.extra_args 95 117 let debug_stderr t = t.debug_stderr 96 118 let hooks t = t.hooks 119 + let max_budget_usd t = t.max_budget_usd 120 + let fallback_model t = t.fallback_model 121 + let setting_sources t = t.setting_sources 122 + let max_buffer_size t = t.max_buffer_size 123 + let user t = t.user 124 + let output_format t = t.output_format 97 125 98 126 let with_allowed_tools tools t = { t with allowed_tools = tools } 99 127 let with_disallowed_tools tools t = { t with disallowed_tools = tools } ··· 103 131 let with_permission_mode mode t = { t with permission_mode = Some mode } 104 132 let with_permission_callback callback t = { t with permission_callback = Some callback } 105 133 let with_model model t = { t with model = Some model } 134 + let with_model_string model t = { t with model = Some (Model.of_string model) } 106 135 let with_cwd cwd t = { t with cwd = Some cwd } 107 136 let with_env env t = { t with env } 108 137 let with_continue_conversation continue t = { t with continue_conversation = continue } ··· 114 143 let with_extra_args args t = { t with extra_args = args } 115 144 let with_debug_stderr sink t = { t with debug_stderr = Some sink } 116 145 let with_hooks hooks t = { t with hooks = Some hooks } 146 + let with_max_budget_usd budget t = { t with max_budget_usd = Some budget } 147 + let with_fallback_model model t = { t with fallback_model = Some model } 148 + let with_fallback_model_string model t = { t with fallback_model = Some (Model.of_string model) } 149 + let with_setting_sources sources t = { t with setting_sources = Some sources } 150 + let with_no_settings t = { t with setting_sources = Some [] } 151 + let with_max_buffer_size size t = { t with max_buffer_size = Some size } 152 + let with_user user t = { t with user = Some user } 153 + let with_output_format format t = { t with output_format = Some format } 117 154 118 155 let to_json t = 119 156 let fields = [] in ··· 145 182 | None -> fields 146 183 in 147 184 let fields = match t.model with 148 - | Some m -> ("model", `String m) :: fields 185 + | Some m -> ("model", `String (Model.to_string m)) :: fields 149 186 | None -> fields 150 187 in 151 188 let fields = ··· 182 219 try Some (Permissions.Mode.of_json (List.assoc "permission_mode" fields)) 183 220 with Not_found -> None 184 221 in 185 - let model = 186 - try Some (get_string (List.assoc "model" fields)) 222 + let model = 223 + try Some (Model.of_string (get_string (List.assoc "model" fields))) 187 224 with Not_found -> None 188 225 in 189 226 let env = ··· 205 242 add_dirs = []; 206 243 extra_args = []; 207 244 debug_stderr = None; 208 - hooks = None; } 245 + hooks = None; 246 + max_budget_usd = None; 247 + fallback_model = None; 248 + setting_sources = None; 249 + max_buffer_size = None; 250 + user = None; 251 + output_format = None; } 209 252 | _ -> raise (Invalid_argument "Options.of_json: expected object") 210 253 211 254 let pp fmt t = ··· 225 268 Fmt.(option string) t.system_prompt 226 269 Fmt.(option string) t.append_system_prompt 227 270 Fmt.(option Permissions.Mode.pp) t.permission_mode 228 - Fmt.(option string) t.model 271 + Fmt.(option Model.pp) t.model 229 272 Fmt.(list (pair string string)) t.env 230 273 231 274 let log_options t =
+177 -10
claudeio/lib/options.mli
··· 1 1 (** Configuration options for Claude sessions. 2 - 3 - This module provides configuration options for controlling Claude's 4 - behavior, including tool permissions, system prompts, models, and 5 - execution environment. *) 2 + 3 + This module provides comprehensive configuration options for controlling 4 + Claude's behavior, including tool permissions, system prompts, models, 5 + execution environment, cost controls, and structured outputs. 6 + 7 + {2 Overview} 8 + 9 + Options control all aspects of Claude's behavior: 10 + - {b Permissions}: Which tools Claude can use and how permission is granted 11 + - {b Models}: Which AI model to use and fallback options 12 + - {b Environment}: Working directory, environment variables, settings 13 + - {b Cost Control}: Budget limits to prevent runaway spending 14 + - {b Hooks}: Intercept and modify tool execution 15 + - {b Structured Output}: JSON schema validation for responses 16 + - {b Session Management}: Continue or resume conversations 17 + 18 + {2 Builder Pattern} 19 + 20 + Options use a functional builder pattern - each [with_*] function returns 21 + a new options value with the specified field updated: 22 + 23 + {[ 24 + let options = Options.default 25 + |> Options.with_model "claude-sonnet-4-5" 26 + |> Options.with_max_budget_usd 1.0 27 + |> Options.with_permission_mode Permissions.Mode.Accept_edits 28 + ]} 29 + 30 + {2 Common Configuration Scenarios} 31 + 32 + {3 CI/CD: Isolated, Reproducible Builds} 33 + 34 + {[ 35 + let ci_config = Options.default 36 + |> Options.with_no_settings (* Ignore user config *) 37 + |> Options.with_max_budget_usd 0.50 (* 50 cent limit *) 38 + |> Options.with_permission_mode 39 + Permissions.Mode.Bypass_permissions 40 + |> Options.with_model "claude-haiku-4" 41 + ]} 42 + 43 + {3 Production: Cost Control with Fallback} 44 + 45 + {[ 46 + let prod_config = Options.default 47 + |> Options.with_model "claude-sonnet-4-5" 48 + |> Options.with_fallback_model "claude-haiku-4" 49 + |> Options.with_max_budget_usd 10.0 (* $10 daily limit *) 50 + |> Options.with_max_buffer_size 5_000_000 51 + ]} 52 + 53 + {3 Development: User Settings with Overrides} 54 + 55 + {[ 56 + let dev_config = Options.default 57 + |> Options.with_setting_sources [User; Project] 58 + |> Options.with_max_budget_usd 1.0 59 + |> Options.with_permission_mode Permissions.Mode.Default 60 + ]} 61 + 62 + {3 Structured Output: Type-Safe Responses} 63 + 64 + {[ 65 + let schema = Ezjsonm.(`O [ 66 + ("type", `String "object"); 67 + ("properties", `O [ 68 + ("count", `O [("type", `String "integer")]); 69 + ("has_tests", `O [("type", `String "boolean")]); 70 + ]); 71 + ]) 72 + let format = Structured_output.of_json_schema schema 73 + 74 + let analysis_config = Options.default 75 + |> Options.with_output_format format 76 + |> Options.with_allowed_tools ["Read"; "Glob"; "Grep"] 77 + ]} 78 + 79 + {2 Advanced Options} 80 + 81 + {3 Budget Control} 82 + 83 + Use {!with_max_budget_usd} to set hard spending limits. Claude will 84 + terminate the session if the budget is exceeded, preventing runaway costs. 85 + 86 + {3 Settings Isolation} 87 + 88 + Use {!with_setting_sources} or {!with_no_settings} to control which 89 + configuration files are loaded: 90 + - [User] - ~/.claude/config 91 + - [Project] - .claude/ in project root 92 + - [Local] - Current directory settings 93 + - [Some \[\]] (via {!with_no_settings}) - No settings, fully isolated 94 + 95 + This is critical for reproducible builds in CI/CD environments. 96 + 97 + {3 Model Fallback} 98 + 99 + Use {!with_fallback_model} to specify an alternative model when the 100 + primary model is unavailable or overloaded. This improves reliability. *) 6 101 7 102 open Ezjsonm 8 103 9 104 (** The log source for options operations *) 10 105 val src : Logs.Src.t 11 106 107 + (** {1 Types} *) 108 + 109 + type setting_source = User | Project | Local 110 + (** Setting source determines which configuration files to load. 111 + - [User]: Load user-level settings from ~/.claude/config 112 + - [Project]: Load project-level settings from .claude/ in project root 113 + - [Local]: Load local settings from current directory *) 114 + 12 115 type t 13 116 (** The type of configuration options. *) 14 117 ··· 27 130 ?append_system_prompt:string -> 28 131 ?permission_mode:Permissions.Mode.t -> 29 132 ?permission_callback:Permissions.callback -> 30 - ?model:string -> 133 + ?model:Model.t -> 31 134 ?cwd:Eio.Fs.dir_ty Eio.Path.t -> 32 135 ?env:(string * string) list -> 33 136 ?continue_conversation:bool -> ··· 39 142 ?extra_args:(string * string option) list -> 40 143 ?debug_stderr:Eio.Flow.sink_ty Eio.Flow.sink -> 41 144 ?hooks:Hooks.config -> 145 + ?max_budget_usd:float -> 146 + ?fallback_model:Model.t -> 147 + ?setting_sources:setting_source list -> 148 + ?max_buffer_size:int -> 149 + ?user:string -> 150 + ?output_format:Structured_output.t -> 42 151 unit -> t 43 152 (** [create ?allowed_tools ?disallowed_tools ?max_thinking_tokens ?system_prompt 44 153 ?append_system_prompt ?permission_mode ?permission_callback ?model ?cwd ?env 45 154 ?continue_conversation ?resume ?max_turns ?permission_prompt_tool_name ?settings 46 - ?add_dirs ?extra_args ?debug_stderr ()] 155 + ?add_dirs ?extra_args ?debug_stderr ?hooks ?max_budget_usd ?fallback_model 156 + ?setting_sources ?max_buffer_size ?user ()] 47 157 creates a new configuration. 48 158 @param allowed_tools List of explicitly allowed tool names 49 159 @param disallowed_tools List of explicitly disallowed tool names ··· 62 172 @param settings Path to settings file 63 173 @param add_dirs Additional directories to allow access to 64 174 @param extra_args Additional CLI flags to pass through 65 - @param debug_stderr Sink for debug output when debug-to-stderr is set *) 175 + @param debug_stderr Sink for debug output when debug-to-stderr is set 176 + @param hooks Hooks configuration for event interception 177 + @param max_budget_usd Hard spending limit in USD (terminates on exceed) 178 + @param fallback_model Automatic fallback on primary model unavailability 179 + @param setting_sources Control which settings load (user/project/local) 180 + @param max_buffer_size Control for stdout buffer size in bytes 181 + @param user Unix user for subprocess execution 182 + @param output_format Optional structured output format specification *) 66 183 67 184 (** {1 Accessors} *) 68 185 ··· 87 204 val permission_callback : t -> Permissions.callback option 88 205 (** [permission_callback t] returns the optional permission callback. *) 89 206 90 - val model : t -> string option 207 + val model : t -> Model.t option 91 208 (** [model t] returns the optional model override. *) 92 209 93 210 val cwd : t -> Eio.Fs.dir_ty Eio.Path.t option ··· 123 240 val hooks : t -> Hooks.config option 124 241 (** [hooks t] returns the optional hooks configuration. *) 125 242 243 + val max_budget_usd : t -> float option 244 + (** [max_budget_usd t] returns the optional spending limit in USD. *) 245 + 246 + val fallback_model : t -> Model.t option 247 + (** [fallback_model t] returns the optional fallback model. *) 248 + 249 + val setting_sources : t -> setting_source list option 250 + (** [setting_sources t] returns the optional list of setting sources to load. *) 251 + 252 + val max_buffer_size : t -> int option 253 + (** [max_buffer_size t] returns the optional stdout buffer size in bytes. *) 254 + 255 + val user : t -> string option 256 + (** [user t] returns the optional Unix user for subprocess execution. *) 257 + 258 + val output_format : t -> Structured_output.t option 259 + (** [output_format t] returns the optional structured output format. *) 260 + 126 261 (** {1 Builders} *) 127 262 128 263 val with_allowed_tools : string list -> t -> t ··· 146 281 val with_permission_callback : Permissions.callback -> t -> t 147 282 (** [with_permission_callback callback t] sets the permission callback. *) 148 283 149 - val with_model : string -> t -> t 150 - (** [with_model model t] sets the model override. *) 284 + val with_model : Model.t -> t -> t 285 + (** [with_model model t] sets the model override using a typed Model.t. *) 286 + 287 + val with_model_string : string -> t -> t 288 + (** [with_model_string model t] sets the model override from a string. 289 + The string is parsed using {!Model.of_string}. *) 151 290 152 291 val with_cwd : Eio.Fs.dir_ty Eio.Path.t -> t -> t 153 292 (** [with_cwd cwd t] sets the working directory. *) ··· 181 320 182 321 val with_hooks : Hooks.config -> t -> t 183 322 (** [with_hooks hooks t] sets the hooks configuration. *) 323 + 324 + val with_max_budget_usd : float -> t -> t 325 + (** [with_max_budget_usd budget t] sets the maximum spending limit in USD. 326 + The session will terminate if this limit is exceeded. *) 327 + 328 + val with_fallback_model : Model.t -> t -> t 329 + (** [with_fallback_model model t] sets the fallback model using a typed Model.t. *) 330 + 331 + val with_fallback_model_string : string -> t -> t 332 + (** [with_fallback_model_string model t] sets the fallback model from a string. 333 + The string is parsed using {!Model.of_string}. *) 334 + 335 + val with_setting_sources : setting_source list -> t -> t 336 + (** [with_setting_sources sources t] sets which configuration sources to load. 337 + Use empty list for isolated environments (e.g., CI/CD). *) 338 + 339 + val with_no_settings : t -> t 340 + (** [with_no_settings t] disables all settings loading (user, project, local). 341 + Useful for CI/CD environments where you want isolated, reproducible behavior. *) 342 + 343 + val with_max_buffer_size : int -> t -> t 344 + (** [with_max_buffer_size size t] sets the maximum stdout buffer size in bytes. *) 345 + 346 + val with_user : string -> t -> t 347 + (** [with_user user t] sets the Unix user for subprocess execution. *) 348 + 349 + val with_output_format : Structured_output.t -> t -> t 350 + (** [with_output_format format t] sets the structured output format. *) 184 351 185 352 (** {1 Serialization} *) 186 353
+91 -4
claudeio/lib/sdk_control.ml
··· 40 40 server_name : string; 41 41 message : value; 42 42 } 43 - 43 + 44 + type set_model = { 45 + subtype : [`Set_model]; 46 + model : string; 47 + } 48 + 49 + type get_server_info = { 50 + subtype : [`Get_server_info]; 51 + } 52 + 44 53 type t = 45 54 | Interrupt of interrupt 46 55 | Permission of permission ··· 48 57 | Set_permission_mode of set_permission_mode 49 58 | Hook_callback of hook_callback 50 59 | Mcp_message of mcp_message 60 + | Set_model of set_model 61 + | Get_server_info of get_server_info 51 62 52 63 let interrupt () = Interrupt { subtype = `Interrupt } 53 64 ··· 80 91 server_name; 81 92 message; 82 93 } 83 - 94 + 95 + let set_model ~model = 96 + Set_model { subtype = `Set_model; model } 97 + 98 + let get_server_info () = 99 + Get_server_info { subtype = `Get_server_info } 100 + 84 101 let to_json = function 85 102 | Interrupt _ -> 86 103 `O [("subtype", `String "interrupt")] ··· 131 148 ("server_name", `String m.server_name); 132 149 ("message", m.message); 133 150 ] 134 - 151 + | Set_model s -> 152 + `O [ 153 + ("subtype", `String "set_model"); 154 + ("model", `String s.model); 155 + ] 156 + | Get_server_info _ -> 157 + `O [("subtype", `String "get_server_info")] 158 + 135 159 let of_json = function 136 160 | `O fields -> 137 161 let subtype = JU.assoc_string "subtype" fields in ··· 183 207 server_name; 184 208 message; 185 209 } 210 + | "set_model" -> 211 + let model = JU.assoc_string "model" fields in 212 + Set_model { subtype = `Set_model; model } 213 + | "get_server_info" -> 214 + Get_server_info { subtype = `Get_server_info } 186 215 | _ -> raise (Invalid_argument ("Unknown request subtype: " ^ subtype))) 187 216 | _ -> raise (Invalid_argument "Request.of_json: expected object") 188 217 ··· 204 233 | Mcp_message m -> 205 234 Fmt.pf fmt "@[<2>McpMessage@ { server = %S }@]" 206 235 m.server_name 236 + | Set_model s -> 237 + Fmt.pf fmt "@[<2>SetModel@ { model = %S }@]" s.model 238 + | Get_server_info _ -> 239 + Fmt.pf fmt "@[<2>GetServerInfo@]" 207 240 end 208 241 209 242 module Response = struct ··· 360 393 Log.debug (fun m -> m "SDK control request: %a" Request.pp req) 361 394 362 395 let log_response resp = 363 - Log.debug (fun m -> m "SDK control response: %a" Response.pp resp) 396 + Log.debug (fun m -> m "SDK control response: %a" Response.pp resp) 397 + 398 + (** Server information *) 399 + module Server_info = struct 400 + type t = { 401 + version : string; 402 + capabilities : string list; 403 + commands : string list; 404 + output_styles : string list; 405 + } 406 + 407 + let create ~version ~capabilities ~commands ~output_styles = 408 + { version; capabilities; commands; output_styles } 409 + 410 + let version t = t.version 411 + let capabilities t = t.capabilities 412 + let commands t = t.commands 413 + let output_styles t = t.output_styles 414 + 415 + let of_json = function 416 + | `O fields -> 417 + let version = JU.assoc_string "version" fields in 418 + let capabilities = 419 + match List.assoc_opt "capabilities" fields with 420 + | Some (`A lst) -> List.map Ezjsonm.get_string lst 421 + | _ -> [] 422 + in 423 + let commands = 424 + match List.assoc_opt "commands" fields with 425 + | Some (`A lst) -> List.map Ezjsonm.get_string lst 426 + | _ -> [] 427 + in 428 + let output_styles = 429 + match List.assoc_opt "outputStyles" fields with 430 + | Some (`A lst) -> List.map Ezjsonm.get_string lst 431 + | _ -> [] 432 + in 433 + { version; capabilities; commands; output_styles } 434 + | _ -> raise (Invalid_argument "Server_info.of_json: expected object") 435 + 436 + let to_json t = 437 + `O [ 438 + ("version", `String t.version); 439 + ("capabilities", `A (List.map (fun s -> `String s) t.capabilities)); 440 + ("commands", `A (List.map (fun s -> `String s) t.commands)); 441 + ("outputStyles", `A (List.map (fun s -> `String s) t.output_styles)); 442 + ] 443 + 444 + let pp fmt t = 445 + Fmt.pf fmt "@[<2>ServerInfo@ { version = %S;@ capabilities = [%a];@ commands = [%a];@ output_styles = [%a] }@]" 446 + t.version 447 + Fmt.(list ~sep:(any ", ") (quote string)) t.capabilities 448 + Fmt.(list ~sep:(any ", ") (quote string)) t.commands 449 + Fmt.(list ~sep:(any ", ") (quote string)) t.output_styles 450 + end
+149 -6
claudeio/lib/sdk_control.mli
··· 1 1 (** SDK Control Protocol for Claude. 2 - 3 - This module defines the typed SDK control protocol for communication 4 - between the SDK and Claude, including request and response types. *) 2 + 3 + This module defines the typed SDK control protocol for bidirectional 4 + communication between the SDK and the Claude CLI. It handles: 5 + 6 + - Permission requests (tool usage authorization) 7 + - Hook callbacks (intercepting and modifying tool execution) 8 + - Dynamic control (changing settings mid-conversation) 9 + - Server introspection (querying capabilities) 10 + 11 + {2 Protocol Overview} 12 + 13 + The SDK control protocol is a JSON-based request/response protocol that 14 + runs alongside the main message stream. It enables: 15 + 16 + 1. {b Callbacks}: Claude asks the SDK for permission or hook execution 17 + 2. {b Control}: SDK changes Claude's behavior dynamically 18 + 3. {b Introspection}: SDK queries server metadata 19 + 20 + {2 Request/Response Flow} 21 + 22 + {v 23 + SDK Claude CLI 24 + | | 25 + |-- Initialize (with hooks) --> | 26 + |<-- Permission Request --------| (for tool usage) 27 + |-- Allow/Deny Response ------> | 28 + | | 29 + |<-- Hook Callback -------------| (pre/post tool) 30 + |-- Hook Result -------------> | 31 + | | 32 + |-- Set Model ---------------> | (dynamic control) 33 + |<-- Success Response ----------| 34 + | | 35 + |-- Get Server Info ----------> | 36 + |<-- Server Info Response ------| 37 + v} 38 + 39 + {2 Usage} 40 + 41 + Most users won't interact with this module directly. The {!Client} module 42 + handles the protocol automatically. However, this module is exposed for: 43 + 44 + - Understanding the control protocol 45 + - Implementing custom control logic 46 + - Debugging control message flow 47 + - Advanced SDK extensions 48 + 49 + {2 Dynamic Control Examples} 50 + 51 + See {!Client.set_permission_mode}, {!Client.set_model}, and 52 + {!Client.get_server_info} for high-level APIs that use this protocol. *) 5 53 6 54 open Ezjsonm 7 55 ··· 53 101 message : value; 54 102 } 55 103 (** MCP server message request. *) 56 - 104 + 105 + type set_model = { 106 + subtype : [`Set_model]; 107 + model : string; 108 + } 109 + (** Request to change the AI model. *) 110 + 111 + type get_server_info = { 112 + subtype : [`Get_server_info]; 113 + } 114 + (** Request to get server information. *) 115 + 57 116 type t = 58 117 | Interrupt of interrupt 59 118 | Permission of permission ··· 61 120 | Set_permission_mode of set_permission_mode 62 121 | Hook_callback of hook_callback 63 122 | Mcp_message of mcp_message 123 + | Set_model of set_model 124 + | Get_server_info of get_server_info 64 125 (** The type of SDK control requests. *) 65 126 66 127 val interrupt : unit -> t ··· 90 151 91 152 val mcp_message : server_name:string -> message:value -> t 92 153 (** [mcp_message ~server_name ~message] creates an MCP message request. *) 93 - 154 + 155 + val set_model : model:string -> t 156 + (** [set_model ~model] creates a model change request. *) 157 + 158 + val get_server_info : unit -> t 159 + (** [get_server_info ()] creates a server info request. *) 160 + 94 161 val to_json : t -> value 95 162 (** [to_json t] converts a request to JSON. *) 96 163 ··· 185 252 (** [log_request req] logs an SDK control request. *) 186 253 187 254 val log_response : Response.t -> unit 188 - (** [log_response resp] logs an SDK control response. *) 255 + (** [log_response resp] logs an SDK control response. *) 256 + 257 + (** {1 Server Information} 258 + 259 + Server information provides metadata about the Claude CLI server, 260 + including version, capabilities, available commands, and output styles. 261 + 262 + {2 Use Cases} 263 + 264 + - Feature detection: Check if specific capabilities are available 265 + - Version compatibility: Ensure minimum version requirements 266 + - Debugging: Log server information for troubleshooting 267 + - Dynamic adaptation: Adjust SDK behavior based on capabilities 268 + 269 + {2 Example} 270 + 271 + {[ 272 + let info = Client.get_server_info client in 273 + Printf.printf "Claude CLI version: %s\n" 274 + (Server_info.version info); 275 + 276 + if List.mem "structured-output" (Server_info.capabilities info) then 277 + Printf.printf "Structured output is supported\n" 278 + else 279 + Printf.printf "Structured output not available\n"; 280 + ]} *) 281 + 282 + module Server_info : sig 283 + (** Server information and capabilities. *) 284 + 285 + type t = { 286 + version : string; 287 + (** Server version string (e.g., "2.0.0") *) 288 + 289 + capabilities : string list; 290 + (** Available server capabilities (e.g., "hooks", "structured-output") *) 291 + 292 + commands : string list; 293 + (** Available CLI commands *) 294 + 295 + output_styles : string list; 296 + (** Supported output formats (e.g., "json", "stream-json") *) 297 + } 298 + (** Server metadata and capabilities. 299 + 300 + This information is useful for feature detection and debugging. *) 301 + 302 + val create : 303 + version:string -> 304 + capabilities:string list -> 305 + commands:string list -> 306 + output_styles:string list -> 307 + t 308 + (** [create ~version ~capabilities ~commands ~output_styles] creates server info. *) 309 + 310 + val version : t -> string 311 + (** [version t] returns the server version. *) 312 + 313 + val capabilities : t -> string list 314 + (** [capabilities t] returns the server capabilities. *) 315 + 316 + val commands : t -> string list 317 + (** [commands t] returns available commands. *) 318 + 319 + val output_styles : t -> string list 320 + (** [output_styles t] returns available output styles. *) 321 + 322 + val of_json : value -> t 323 + (** [of_json json] parses server info from JSON. 324 + @raise Invalid_argument if the JSON is not valid server info. *) 325 + 326 + val to_json : t -> value 327 + (** [to_json t] converts server info to JSON. *) 328 + 329 + val pp : Format.formatter -> t -> unit 330 + (** [pp fmt t] pretty-prints the server info. *) 331 + end
+45 -9
claudeio/lib/transport.ml
··· 17 17 sw : Switch.t; 18 18 } 19 19 20 + let setting_source_to_string = function 21 + | Options.User -> "user" 22 + | Options.Project -> "project" 23 + | Options.Local -> "local" 24 + 20 25 let build_command ~claude_path ~options = 21 26 let cmd = [claude_path; "--output-format"; "stream-json"; "--verbose"] in 22 - 27 + 23 28 let cmd = match Options.system_prompt options with 24 29 | Some prompt -> cmd @ ["--system-prompt"; prompt] 25 30 | None -> cmd 26 31 in 27 - 32 + 28 33 let cmd = match Options.append_system_prompt options with 29 34 | Some prompt -> cmd @ ["--append-system-prompt"; prompt] 30 35 | None -> cmd 31 36 in 32 - 37 + 33 38 let cmd = match Options.allowed_tools options with 34 39 | [] -> cmd 35 40 | tools -> cmd @ ["--allowedTools"; String.concat "," tools] 36 41 in 37 - 42 + 38 43 let cmd = match Options.disallowed_tools options with 39 44 | [] -> cmd 40 45 | tools -> cmd @ ["--disallowedTools"; String.concat "," tools] 41 46 in 42 - 47 + 43 48 let cmd = match Options.model options with 44 - | Some model -> cmd @ ["--model"; model] 49 + | Some model -> cmd @ ["--model"; Model.to_string model] 45 50 | None -> cmd 46 51 in 47 - 52 + 48 53 let cmd = match Options.permission_mode options with 49 54 | Some mode -> 50 55 let mode_str = Permissions.Mode.to_string mode in ··· 57 62 | None -> cmd 58 63 in 59 64 65 + (* Advanced configuration options *) 66 + let cmd = match Options.max_budget_usd options with 67 + | Some budget -> cmd @ ["--max-budget-usd"; Float.to_string budget] 68 + | None -> cmd 69 + in 70 + 71 + let cmd = match Options.fallback_model options with 72 + | Some model -> cmd @ ["--fallback-model"; Model.to_string model] 73 + | None -> cmd 74 + in 75 + 76 + let cmd = match Options.setting_sources options with 77 + | Some sources -> 78 + let sources_str = String.concat "," (List.map setting_source_to_string sources) in 79 + cmd @ ["--setting-sources"; sources_str] 80 + | None -> cmd 81 + in 82 + 83 + (* Add JSON Schema if specified *) 84 + let cmd = match Options.output_format options with 85 + | Some format -> 86 + let schema = Structured_output.json_schema format in 87 + let schema_str = Ezjsonm.value_to_string schema in 88 + cmd @ ["--json-schema"; schema_str] 89 + | None -> cmd 90 + in 91 + 60 92 (* Use streaming input mode *) 61 93 cmd @ ["--input-format"; "stream-json"] 62 94 ··· 121 153 122 154 let stdin = (stdin_w :> Eio.Flow.sink_ty r) in 123 155 let stdin_close = (stdin_w :> [`Close | `Flow] r) in 124 - let stdout = Eio.Buf_read.of_flow ~max_size:1_000_000 (stdout_r :> Eio.Flow.source_ty r) in 125 - 156 + let max_size = match Options.max_buffer_size options with 157 + | Some size -> size 158 + | None -> 1_000_000 (* Default 1MB *) 159 + in 160 + let stdout = Eio.Buf_read.of_flow ~max_size (stdout_r :> Eio.Flow.source_ty r) in 161 + 126 162 { process = P process; stdin; stdin_close; stdout; sw } 127 163 128 164 let send t json =
+1 -1
claudeio/test/camel_jokes.ml
··· 45 45 46 46 let run_claude ~sw ~env name prompt = 47 47 Log.info (fun m -> m "🐪 Starting %s..." name); 48 - let options = Claude.Options.create ~model:"sonnet" ~allowed_tools:[] () in 48 + let options = Claude.Options.create ~model:(Claude.Model.of_string "sonnet") ~allowed_tools:[] () in 49 49 50 50 let client = Claude.Client.create ~options ~sw 51 51 ~process_mgr:env#process_mgr
+1 -1
claudeio/test/discovery_demo.ml
··· 37 37 Log.app (fun m -> m "This will discover what permissions Claude needs.\n"); 38 38 39 39 (* Create client with discovery mode *) 40 - let options = Claude.Options.create ~model:"sonnet" () in 40 + let options = Claude.Options.create ~model:(Claude.Model.of_string "sonnet") () in 41 41 let client = Claude.Client.discover_permissions 42 42 (Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ()) in 43 43
+27 -1
claudeio/test/dune
··· 38 38 (public_name hooks_example) 39 39 (name hooks_example) 40 40 (modules hooks_example) 41 - (libraries claude eio_main cmdliner logs logs.fmt fmt.tty fmt.cli logs.cli)) 41 + (libraries claude eio_main cmdliner logs logs.fmt fmt.tty fmt.cli logs.cli)) 42 + 43 + (executable 44 + (public_name dynamic_control_demo) 45 + (name dynamic_control_demo) 46 + (modules dynamic_control_demo) 47 + (libraries claude eio_main cmdliner logs logs.fmt fmt.tty fmt.cli logs.cli)) 48 + 49 + (executable 50 + (public_name advanced_config_demo) 51 + (name advanced_config_demo) 52 + (modules advanced_config_demo) 53 + (libraries claude eio_main logs logs.fmt fmt.tty)) 54 + 55 + (executable 56 + (public_name structured_output_demo) 57 + (name structured_output_demo) 58 + (modules structured_output_demo) 59 + (flags (:standard -w -33)) 60 + (libraries claude eio_main logs logs.fmt fmt.tty)) 61 + 62 + (executable 63 + (public_name structured_output_simple) 64 + (name structured_output_simple) 65 + (modules structured_output_simple) 66 + (flags (:standard -w -33)) 67 + (libraries claude eio_main logs logs.fmt fmt.tty))
+1 -1
claudeio/test/hooks_example.ml
··· 51 51 in 52 52 53 53 let options = Claude.Options.create 54 - ~model:"sonnet" 54 + ~model:(Claude.Model.of_string "sonnet") 55 55 ~hooks 56 56 () in 57 57
+1 -1
claudeio/test/permission_demo.ml
··· 131 131 (* DON'T specify allowed_tools - let the permission callback handle everything. 132 132 The Default permission mode with a callback should send requests for all tools. *) 133 133 let options = Claude.Options.create 134 - ~model:"sonnet" 134 + ~model:(Claude.Model.of_string "sonnet") 135 135 ~permission_mode:Claude.Permissions.Mode.Default 136 136 ~permission_callback:interactive_permission_callback 137 137 () in
+1 -1
claudeio/test/simple_permission_test.ml
··· 17 17 18 18 (* Create options with permission callback *) 19 19 let options = Claude.Options.create 20 - ~model:"sonnet" 20 + ~model:(Claude.Model.of_string "sonnet") 21 21 ~permission_callback:auto_allow_callback 22 22 () in 23 23
+1 -1
claudeio/test/test_permissions.ml
··· 14 14 15 15 (* Create options with custom permission callback *) 16 16 let options = Claude.Options.create 17 - ~model:"sonnet" 17 + ~model:(Claude.Model.of_string "sonnet") 18 18 ~permission_callback:auto_allow_callback 19 19 () in 20 20