···11+module Model = Model
12module Content_block = Content_block
23module Message = Message
34module Control = Control
45module Permissions = Permissions
56module Hooks = Hooks
77+module Sdk_control = Sdk_control
88+module Structured_output = Structured_output
69module Options = Options
710module Transport = Transport
811module Client = Client
+9
claudeio/lib/claude.mli
···153153module Options = Options
154154(** Configuration options for Claude sessions. *)
155155156156+module Model = Model
157157+(** Claude AI model identifiers with type-safe variants. *)
158158+156159module Content_block = Content_block
157160(** Content blocks for messages (text, tool use, tool results, thinking). *)
158161···167170168171module Hooks = Hooks
169172(** Hooks system for event interception. *)
173173+174174+module Sdk_control = Sdk_control
175175+(** SDK control protocol for dynamic configuration. *)
176176+177177+module Structured_output = Structured_output
178178+(** Structured output support using JSON Schema. *)
170179171180module Transport = Transport
172181(** Low-level transport layer for CLI communication. *)
+80-1
claudeio/lib/client.ml
···88 hook_callbacks : (string, Hooks.callback) Hashtbl.t;
99 mutable next_callback_id : int;
1010 mutable session_id : string option;
1111+ control_responses : (string, Ezjsonm.value) Hashtbl.t;
1212+ control_mutex : Eio.Mutex.t;
1313+ control_condition : Eio.Condition.t;
1114}
12151316let handle_control_request t control_msg =
···166169 (* Handle control responses (e.g., initialize response) *)
167170 let request_id = Json_utils.find_string json ["response"; "request_id"] in
168171 Log.debug (fun m -> m "Received control response for request_id: %s" request_id);
169169- (* Don't yield control responses as messages, just loop *)
172172+ (* Store the response and signal waiting threads *)
173173+ Eio.Mutex.use_rw ~protect:false t.control_mutex (fun () ->
174174+ Hashtbl.replace t.control_responses request_id json;
175175+ Eio.Condition.broadcast t.control_condition
176176+ );
170177 loop ()
171178172179 | _ ->
···217224 hook_callbacks;
218225 next_callback_id = 0;
219226 session_id = None;
227227+ control_responses = Hashtbl.create 16;
228228+ control_mutex = Eio.Mutex.create ();
229229+ control_condition = Eio.Condition.create ();
220230 } in
221231222232 (* Register hooks and send initialize if hooks are configured *)
···310320311321let with_permission_callback t callback =
312322 { t with permission_callback = Some callback }
323323+324324+(* Helper to send a control request and wait for response *)
325325+let send_control_request t ~request_id request =
326326+ let open Ezjsonm in
327327+ (* Send the control request *)
328328+ let control_msg = Sdk_control.create_request ~request_id ~request in
329329+ let json = Sdk_control.to_json control_msg in
330330+ Log.info (fun m -> m "Sending control request: %s" (value_to_string json));
331331+ Transport.send t.transport json;
332332+333333+ (* Wait for the response with timeout *)
334334+ let max_wait = 10.0 in (* 10 seconds timeout *)
335335+ let start_time = Unix.gettimeofday () in
336336+337337+ let rec wait_for_response () =
338338+ Eio.Mutex.use_rw ~protect:false t.control_mutex (fun () ->
339339+ match Hashtbl.find_opt t.control_responses request_id with
340340+ | Some response_json ->
341341+ (* Remove it from the table *)
342342+ Hashtbl.remove t.control_responses request_id;
343343+ response_json
344344+ | None ->
345345+ let elapsed = Unix.gettimeofday () -. start_time in
346346+ if elapsed > max_wait then
347347+ raise (Failure (Printf.sprintf "Timeout waiting for control response: %s" request_id))
348348+ else (
349349+ (* Release mutex and wait for signal *)
350350+ Eio.Condition.await_no_mutex t.control_condition;
351351+ wait_for_response ()
352352+ )
353353+ )
354354+ in
355355+356356+ let response_json = wait_for_response () in
357357+ Log.debug (fun m -> m "Received control response: %s" (value_to_string response_json));
358358+359359+ (* Parse the response *)
360360+ let response = find response_json ["response"] |> Sdk_control.Response.of_json in
361361+ match response with
362362+ | Sdk_control.Response.Success s -> s.response
363363+ | Sdk_control.Response.Error e ->
364364+ raise (Failure (Printf.sprintf "Control request failed: %s" e.error))
365365+366366+let set_permission_mode t mode =
367367+ let request_id = Printf.sprintf "set_perm_mode_%f" (Unix.gettimeofday ()) in
368368+ let request = Sdk_control.Request.set_permission_mode ~mode in
369369+ let _response = send_control_request t ~request_id request in
370370+ Log.info (fun m -> m "Permission mode set to: %a" Permissions.Mode.pp mode)
371371+372372+let set_model t model =
373373+ let model_str = Model.to_string model in
374374+ let request_id = Printf.sprintf "set_model_%f" (Unix.gettimeofday ()) in
375375+ let request = Sdk_control.Request.set_model ~model:model_str in
376376+ let _response = send_control_request t ~request_id request in
377377+ Log.info (fun m -> m "Model set to: %a" Model.pp model)
378378+379379+let set_model_string t model_str =
380380+ set_model t (Model.of_string model_str)
381381+382382+let get_server_info t =
383383+ let request_id = Printf.sprintf "get_server_info_%f" (Unix.gettimeofday ()) in
384384+ let request = Sdk_control.Request.get_server_info () in
385385+ match send_control_request t ~request_id request with
386386+ | Some response_data ->
387387+ let server_info = Sdk_control.Server_info.of_json response_data in
388388+ Log.info (fun m -> m "Retrieved server info: %a" Sdk_control.Server_info.pp server_info);
389389+ server_info
390390+ | None ->
391391+ raise (Failure "No response data from get_server_info request")
+193-3
claudeio/lib/client.mli
···11+(** Client interface for interacting with Claude.
22+33+ This module provides the high-level client API for sending messages to
44+ Claude and receiving responses. It handles the bidirectional streaming
55+ protocol, permission callbacks, and hooks.
66+77+ {2 Basic Usage}
88+99+ {[
1010+ Eio.Switch.run @@ fun sw ->
1111+ let client = Client.create ~sw ~process_mgr () in
1212+ Client.query client "What is 2+2?";
1313+1414+ let messages = Client.receive_all client in
1515+ List.iter (function
1616+ | Message.Assistant msg ->
1717+ Printf.printf "Claude: %s\n" (Message.Assistant.text msg)
1818+ | _ -> ()
1919+ ) messages
2020+ ]}
2121+2222+ {2 Features}
2323+2424+ - {b Message Streaming}: Messages are streamed lazily via {!Seq.t}
2525+ - {b Permission Control}: Custom permission callbacks for tool usage
2626+ - {b Hooks}: Intercept and modify tool execution
2727+ - {b Dynamic Control}: Change settings mid-conversation
2828+ - {b Resource Management}: Automatic cleanup via Eio switches
2929+3030+ {2 Message Flow}
3131+3232+ 1. Create a client with {!create}
3333+ 2. Send messages with {!query} or {!send_message}
3434+ 3. Receive responses with {!receive} or {!receive_all}
3535+ 4. Continue multi-turn conversations by sending more messages
3636+ 5. Client automatically cleans up when the switch exits
3737+3838+ {2 Advanced Features}
3939+4040+ - Permission discovery mode for understanding required permissions
4141+ - Mid-conversation model switching and permission mode changes
4242+ - Server capability introspection *)
4343+144(** The log source for client operations *)
245val src : Logs.Src.t
346447type t
4848+(** The type of Claude clients. *)
54966-val create :
77- ?options:Options.t ->
88- sw:Eio.Switch.t ->
5050+val create :
5151+ ?options:Options.t ->
5252+ sw:Eio.Switch.t ->
953 process_mgr:_ Eio.Process.mgr ->
1054 unit -> t
5555+(** [create ?options ~sw ~process_mgr ()] creates a new Claude client.
5656+5757+ @param options Configuration options (defaults to {!Options.default})
5858+ @param sw Eio switch for resource management
5959+ @param process_mgr Eio process manager for spawning the Claude CLI *)
11601261val query : t -> string -> unit
6262+(** [query t prompt] sends a text message to Claude.
6363+6464+ This is a convenience function for simple string messages. For more
6565+ complex messages with tool results or multiple content blocks, use
6666+ {!send_message} instead. *)
6767+1368val send_message : t -> Message.t -> unit
6969+(** [send_message t msg] sends a message to Claude.
7070+7171+ Supports all message types including user messages with tool results. *)
7272+1473val send_user_message : t -> Message.User.t -> unit
7474+(** [send_user_message t msg] sends a user message to Claude. *)
15751676val receive : t -> Message.t Seq.t
7777+(** [receive t] returns a lazy sequence of messages from Claude.
7878+7979+ The sequence yields messages as they arrive from Claude, including:
8080+ - {!Message.Assistant} - Claude's responses
8181+ - {!Message.System} - System notifications
8282+ - {!Message.Result} - Final result with usage statistics
8383+8484+ Control messages (permission requests, hook callbacks) are handled
8585+ internally and not yielded to the sequence. *)
8686+1787val receive_all : t -> Message.t list
8888+(** [receive_all t] collects all messages into a list.
8989+9090+ This is a convenience function that consumes the {!receive} sequence.
9191+ Use this when you want to process all messages at once rather than
9292+ streaming them. *)
18931994val interrupt : t -> unit
9595+(** [interrupt t] sends an interrupt signal to stop Claude's execution. *)
20962197val discover_permissions : t -> t
9898+(** [discover_permissions t] enables permission discovery mode.
9999+100100+ In discovery mode, all tool usage is logged but allowed. Use
101101+ {!get_discovered_permissions} to retrieve the list of permissions
102102+ that were requested during execution.
103103+104104+ This is useful for understanding what permissions your prompt requires. *)
105105+22106val get_discovered_permissions : t -> Permissions.Rule.t list
107107+(** [get_discovered_permissions t] returns permissions discovered during execution.
108108+109109+ Only useful after enabling {!discover_permissions}. *)
110110+23111val with_permission_callback : t -> Permissions.callback -> t
112112+(** [with_permission_callback t callback] updates the permission callback.
113113+114114+ Allows dynamically changing the permission callback without recreating
115115+ the client. *)
116116+117117+(** {1 Dynamic Control Methods}
118118+119119+ These methods allow you to change Claude's behavior mid-conversation
120120+ without recreating the client. This is useful for:
121121+122122+ - Adjusting permission strictness based on user feedback
123123+ - Switching to faster/cheaper models for simple tasks
124124+ - Adapting to changing requirements during long conversations
125125+ - Introspecting server capabilities
126126+127127+ {2 Example: Adaptive Permission Control}
128128+129129+ {[
130130+ (* Start with strict permissions *)
131131+ let client = Client.create ~sw ~process_mgr
132132+ ~options:(Options.default
133133+ |> Options.with_permission_mode Permissions.Mode.Default) ()
134134+ in
135135+136136+ Client.query client "Analyze this code";
137137+ let _ = Client.receive_all client in
138138+139139+ (* User approves, switch to auto-accept edits *)
140140+ Client.set_permission_mode client Permissions.Mode.Accept_edits;
141141+142142+ Client.query client "Now refactor it";
143143+ let _ = Client.receive_all client in
144144+ ]}
145145+146146+ {2 Example: Model Switching for Efficiency}
147147+148148+ {[
149149+ (* Use powerful model for complex analysis *)
150150+ let client = Client.create ~sw ~process_mgr
151151+ ~options:(Options.default |> Options.with_model "claude-sonnet-4-5") ()
152152+ in
153153+154154+ Client.query client "Design a new architecture for this system";
155155+ let _ = Client.receive_all client in
156156+157157+ (* Switch to faster model for simple tasks *)
158158+ Client.set_model client "claude-haiku-4";
159159+160160+ Client.query client "Now write a README";
161161+ let _ = Client.receive_all client in
162162+ ]}
163163+164164+ {2 Example: Server Introspection}
165165+166166+ {[
167167+ let info = Client.get_server_info client in
168168+ Printf.printf "Claude CLI version: %s\n"
169169+ (Sdk_control.Server_info.version info);
170170+ Printf.printf "Capabilities: %s\n"
171171+ (String.concat ", " (Sdk_control.Server_info.capabilities info));
172172+ ]} *)
173173+174174+val set_permission_mode : t -> Permissions.Mode.t -> unit
175175+(** [set_permission_mode t mode] changes the permission mode mid-conversation.
176176+177177+ This allows switching between permission modes without recreating the client:
178178+ - {!Permissions.Mode.Default} - Prompt for all permissions
179179+ - {!Permissions.Mode.Accept_edits} - Auto-accept file edits
180180+ - {!Permissions.Mode.Plan} - Planning mode with restricted execution
181181+ - {!Permissions.Mode.Bypass_permissions} - Skip all permission checks
182182+183183+ @raise Failure if the server returns an error *)
184184+185185+val set_model : t -> Model.t -> unit
186186+(** [set_model t model] switches to a different AI model mid-conversation.
187187+188188+ Common models:
189189+ - [`Sonnet_4_5] - Most capable, balanced performance
190190+ - [`Opus_4] - Maximum capability for complex tasks
191191+ - [`Haiku_4] - Fast and cost-effective
192192+193193+ @raise Failure if the model is invalid or unavailable *)
194194+195195+val set_model_string : t -> string -> unit
196196+(** [set_model_string t model] switches to a different AI model using a string.
197197+198198+ This is a convenience function that parses the string using {!Model.of_string}.
199199+200200+ @raise Failure if the model is invalid or unavailable *)
201201+202202+val get_server_info : t -> Sdk_control.Server_info.t
203203+(** [get_server_info t] retrieves server capabilities and metadata.
204204+205205+ Returns information about:
206206+ - Server version string
207207+ - Available capabilities
208208+ - Supported commands
209209+ - Available output styles
210210+211211+ Useful for feature detection and debugging.
212212+213213+ @raise Failure if the server returns an error *)
···9898end
9999100100module Assistant = struct
101101+ type error = [
102102+ | `Authentication_failed
103103+ | `Billing_error
104104+ | `Rate_limit
105105+ | `Invalid_request
106106+ | `Server_error
107107+ | `Unknown
108108+ ]
109109+110110+ let error_to_string = function
111111+ | `Authentication_failed -> "authentication_failed"
112112+ | `Billing_error -> "billing_error"
113113+ | `Rate_limit -> "rate_limit"
114114+ | `Invalid_request -> "invalid_request"
115115+ | `Server_error -> "server_error"
116116+ | `Unknown -> "unknown"
117117+118118+ let error_of_string = function
119119+ | "authentication_failed" -> `Authentication_failed
120120+ | "billing_error" -> `Billing_error
121121+ | "rate_limit" -> `Rate_limit
122122+ | "invalid_request" -> `Invalid_request
123123+ | "server_error" -> `Server_error
124124+ | "unknown" | _ -> `Unknown
125125+101126 type t = {
102127 content : Content_block.t list;
103128 model : string;
129129+ error : error option;
104130 }
105105-106106- let create ~content ~model = { content; model }
131131+132132+ let create ~content ~model ?error () = { content; model; error }
107133 let content t = t.content
108134 let model t = t.model
135135+ let error t = t.error
109136110137 let get_text_blocks t =
111138 List.filter_map (function
···135162 String.concat "\n" (get_text_blocks t)
136163137164 let to_json t =
165165+ let msg_fields = [
166166+ ("content", `A (List.map Content_block.to_json t.content));
167167+ ("model", `String t.model);
168168+ ] in
169169+ let msg_fields = match t.error with
170170+ | Some err -> ("error", `String (error_to_string err)) :: msg_fields
171171+ | None -> msg_fields
172172+ in
138173 `O [
139174 ("type", `String "assistant");
140140- ("message", `O [
141141- ("content", `A (List.map Content_block.to_json t.content));
142142- ("model", `String t.model);
143143- ]);
175175+ ("message", `O msg_fields);
144176 ]
145177146178 let of_json = function
147179 | `O fields ->
148180 let message = List.assoc "message" fields in
149149- let content, model = match message with
181181+ let content, model, error = match message with
150182 | `O msg_fields ->
151151- let content =
183183+ let content =
152184 match List.assoc "content" msg_fields with
153185 | `A blocks -> List.map Content_block.of_json blocks
154186 | _ -> raise (Invalid_argument "Assistant.of_json: invalid content")
155187 in
156188 let model = JU.assoc_string "model" msg_fields in
157157- content, model
189189+ let error =
190190+ match JU.assoc_string_opt "error" msg_fields with
191191+ | Some err_str -> Some (error_of_string err_str)
192192+ | None -> None
193193+ in
194194+ content, model, error
158195 | _ -> raise (Invalid_argument "Assistant.of_json: invalid message")
159196 in
160160- { content; model }
197197+ { content; model; error }
161198 | _ -> raise (Invalid_argument "Assistant.of_json: expected object")
162199163200 let pp fmt t =
···341378 total_cost_usd : float option;
342379 usage : Usage.t option;
343380 result : string option;
381381+ structured_output : value option;
344382 }
345383346346- let create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
347347- ~session_id ?total_cost_usd ?usage ?result () =
384384+ let create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
385385+ ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
348386 { subtype; duration_ms; duration_api_ms; is_error; num_turns;
349349- session_id; total_cost_usd; usage; result }
387387+ session_id; total_cost_usd; usage; result; structured_output }
350388351389 let subtype t = t.subtype
352390 let duration_ms t = t.duration_ms
···357395 let total_cost_usd t = t.total_cost_usd
358396 let usage t = t.usage
359397 let result t = t.result
398398+ let structured_output t = t.structured_output
360399361400 let to_json t =
362401 let fields = [
···380419 | Some result -> ("result", `String result) :: fields
381420 | None -> fields
382421 in
422422+ let fields = match t.structured_output with
423423+ | Some output -> ("structured_output", output) :: fields
424424+ | None -> fields
425425+ in
383426 `O fields
384427385428 let of_json = function
···393436 let total_cost_usd = JU.assoc_float_opt "total_cost_usd" fields in
394437 let usage = Option.map Usage.of_json (List.assoc_opt "usage" fields) in
395438 let result = JU.assoc_string_opt "result" fields in
439439+ let structured_output = List.assoc_opt "structured_output" fields in
396440 { subtype; duration_ms; duration_api_ms; is_error; num_turns;
397397- session_id; total_cost_usd; usage; result }
441441+ session_id; total_cost_usd; usage; result; structured_output }
398442 | _ -> raise (Invalid_argument "Result.of_json: expected object")
399443400444 let pp fmt t =
···434478let user_with_tool_result ~tool_use_id ~content ?is_error () =
435479 User (User.create_with_tool_result ~tool_use_id ~content ?is_error ())
436480437437-let assistant ~content ~model = Assistant (Assistant.create ~content ~model)
438438-let assistant_text ~text ~model =
439439- Assistant (Assistant.create ~content:[Content_block.text text] ~model)
481481+let assistant ~content ~model ?error () = Assistant (Assistant.create ~content ~model ?error ())
482482+let assistant_text ~text ~model ?error () =
483483+ Assistant (Assistant.create ~content:[Content_block.text text] ~model ?error ())
440484441485let system ~subtype ~data = System (System.create ~subtype ~data)
442486let system_init ~session_id =
···446490 let data = System.Data.of_assoc [("error", `String error)] in
447491 System (System.create ~subtype:"error" ~data)
448492449449-let result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
450450- ~session_id ?total_cost_usd ?usage ?result () =
451451- Result (Result.create ~subtype ~duration_ms ~duration_api_ms ~is_error
452452- ~num_turns ~session_id ?total_cost_usd ?usage ?result ())
493493+let result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
494494+ ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
495495+ Result (Result.create ~subtype ~duration_ms ~duration_api_ms ~is_error
496496+ ~num_turns ~session_id ?total_cost_usd ?usage ?result ?structured_output ())
453497454498let to_json = function
455499 | User t -> User.to_json t
+59-33
claudeio/lib/message.mli
···62626363module Assistant : sig
6464 (** Messages from Claude assistant. *)
6565-6565+6666+ type error = [
6767+ | `Authentication_failed (** Authentication with Claude API failed *)
6868+ | `Billing_error (** Billing or account issue *)
6969+ | `Rate_limit (** Rate limit exceeded *)
7070+ | `Invalid_request (** Request was invalid *)
7171+ | `Server_error (** Internal server error *)
7272+ | `Unknown (** Unknown error type *)
7373+ ]
7474+ (** The type of assistant message errors based on Python SDK error types. *)
7575+7676+ val error_to_string : error -> string
7777+ (** [error_to_string err] converts an error to its string representation. *)
7878+7979+ val error_of_string : string -> error
8080+ (** [error_of_string s] parses an error string. Unknown strings become [`Unknown]. *)
8181+6682 type t
6783 (** The type of assistant messages. *)
6868-6969- val create : content:Content_block.t list -> model:string -> t
7070- (** [create ~content ~model] creates an assistant message.
8484+8585+ val create : content:Content_block.t list -> model:string -> ?error:error -> unit -> t
8686+ (** [create ~content ~model ?error ()] creates an assistant message.
7187 @param content List of content blocks in the response
7272- @param model The model identifier used for the response *)
7373-8888+ @param model The model identifier used for the response
8989+ @param error Optional error that occurred during message generation *)
9090+7491 val content : t -> Content_block.t list
7592 (** [content t] returns the content blocks of the assistant message. *)
7676-9393+7794 val model : t -> string
7895 (** [model t] returns the model identifier. *)
9696+9797+ val error : t -> error option
9898+ (** [error t] returns the optional error that occurred during message generation. *)
799980100 val get_text_blocks : t -> string list
81101 (** [get_text_blocks t] extracts all text content from the message. *)
···230250 type t
231251 (** The type of result messages. *)
232252233233- val create :
234234- subtype:string ->
235235- duration_ms:int ->
236236- duration_api_ms:int ->
237237- is_error:bool ->
238238- num_turns:int ->
239239- session_id:string ->
240240- ?total_cost_usd:float ->
241241- ?usage:Usage.t ->
242242- ?result:string ->
253253+ val create :
254254+ subtype:string ->
255255+ duration_ms:int ->
256256+ duration_api_ms:int ->
257257+ is_error:bool ->
258258+ num_turns:int ->
259259+ session_id:string ->
260260+ ?total_cost_usd:float ->
261261+ ?usage:Usage.t ->
262262+ ?result:string ->
263263+ ?structured_output:Ezjsonm.value ->
243264 unit -> t
244265 (** [create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
245266 ~session_id ?total_cost_usd ?usage ?result ()] creates a result message.
···251272 @param session_id Unique session identifier
252273 @param total_cost_usd Optional total cost in USD
253274 @param usage Optional usage statistics as JSON
254254- @param result Optional result string *)
275275+ @param result Optional result string
276276+ @param structured_output Optional structured JSON output from Claude *)
255277256278 val subtype : t -> string
257279 (** [subtype t] returns the subtype of the result. *)
···279301280302 val result : t -> string option
281303 (** [result t] returns the optional result string. *)
282282-304304+305305+ val structured_output : t -> Ezjsonm.value option
306306+ (** [structured_output t] returns the optional structured JSON output. *)
307307+283308 val to_json : t -> Ezjsonm.value
284309 (** [to_json t] converts the result message to its JSON representation. *)
285310···310335(** [user_with_tool_result ~tool_use_id ~content ?is_error ()] creates a user message
311336 containing a tool result. *)
312337313313-val assistant : content:Content_block.t list -> model:string -> t
314314-(** [assistant ~content ~model] creates an assistant message. *)
338338+val assistant : content:Content_block.t list -> model:string -> ?error:Assistant.error -> unit -> t
339339+(** [assistant ~content ~model ?error ()] creates an assistant message. *)
315340316316-val assistant_text : text:string -> model:string -> t
317317-(** [assistant_text ~text ~model] creates an assistant message with only text content. *)
341341+val assistant_text : text:string -> model:string -> ?error:Assistant.error -> unit -> t
342342+(** [assistant_text ~text ~model ?error ()] creates an assistant message with only text content. *)
318343319344val system : subtype:string -> data:System.Data.t -> t
320345(** [system ~subtype ~data] creates a system message. *)
···325350val system_error : error:string -> t
326351(** [system_error ~error] creates a system error message. *)
327352328328-val result :
329329- subtype:string ->
330330- duration_ms:int ->
331331- duration_api_ms:int ->
332332- is_error:bool ->
333333- num_turns:int ->
334334- session_id:string ->
335335- ?total_cost_usd:float ->
336336- ?usage:Result.Usage.t ->
337337- ?result:string ->
353353+val result :
354354+ subtype:string ->
355355+ duration_ms:int ->
356356+ duration_api_ms:int ->
357357+ is_error:bool ->
358358+ num_turns:int ->
359359+ session_id:string ->
360360+ ?total_cost_usd:float ->
361361+ ?usage:Result.Usage.t ->
362362+ ?result:string ->
363363+ ?structured_output:Ezjsonm.value ->
338364 unit -> t
339365(** [result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
340366 ~session_id ?total_cost_usd ?usage ?result ()] creates a result message. *)
+50-7
claudeio/lib/options.ml
···33let src = Logs.Src.create "claude.options" ~doc:"Claude configuration options"
44module Log = (val Logs.src_log src : Logs.LOG)
5566+type setting_source = User | Project | Local
77+68type t = {
79 allowed_tools : string list;
810 disallowed_tools : string list;
···1113 append_system_prompt : string option;
1214 permission_mode : Permissions.Mode.t option;
1315 permission_callback : Permissions.callback option;
1414- model : string option;
1616+ model : Model.t option;
1517 cwd : Eio.Fs.dir_ty Eio.Path.t option;
1618 env : (string * string) list;
1719 continue_conversation : bool;
···2325 extra_args : (string * string option) list;
2426 debug_stderr : Eio.Flow.sink_ty Eio.Flow.sink option;
2527 hooks : Hooks.config option;
2828+ max_budget_usd : float option;
2929+ fallback_model : Model.t option;
3030+ setting_sources : setting_source list option;
3131+ max_buffer_size : int option;
3232+ user : string option;
3333+ output_format : Structured_output.t option;
2634}
27352836let default = {
···4553 extra_args = [];
4654 debug_stderr = None;
4755 hooks = None;
5656+ max_budget_usd = None;
5757+ fallback_model = None;
5858+ setting_sources = None;
5959+ max_buffer_size = None;
6060+ user = None;
6161+ output_format = None;
4862}
49635064let create
···6781 ?(extra_args = [])
6882 ?debug_stderr
6983 ?hooks
8484+ ?max_budget_usd
8585+ ?fallback_model
8686+ ?setting_sources
8787+ ?max_buffer_size
8888+ ?user
8989+ ?output_format
7090 () =
7191 { allowed_tools; disallowed_tools; max_thinking_tokens;
7292 system_prompt; append_system_prompt; permission_mode;
7393 permission_callback; model; cwd; env;
7494 continue_conversation; resume; max_turns;
7595 permission_prompt_tool_name; settings; add_dirs;
7676- extra_args; debug_stderr; hooks }
9696+ extra_args; debug_stderr; hooks;
9797+ max_budget_usd; fallback_model; setting_sources;
9898+ max_buffer_size; user; output_format }
779978100let allowed_tools t = t.allowed_tools
79101let disallowed_tools t = t.disallowed_tools
···94116let extra_args t = t.extra_args
95117let debug_stderr t = t.debug_stderr
96118let hooks t = t.hooks
119119+let max_budget_usd t = t.max_budget_usd
120120+let fallback_model t = t.fallback_model
121121+let setting_sources t = t.setting_sources
122122+let max_buffer_size t = t.max_buffer_size
123123+let user t = t.user
124124+let output_format t = t.output_format
9712598126let with_allowed_tools tools t = { t with allowed_tools = tools }
99127let with_disallowed_tools tools t = { t with disallowed_tools = tools }
···103131let with_permission_mode mode t = { t with permission_mode = Some mode }
104132let with_permission_callback callback t = { t with permission_callback = Some callback }
105133let with_model model t = { t with model = Some model }
134134+let with_model_string model t = { t with model = Some (Model.of_string model) }
106135let with_cwd cwd t = { t with cwd = Some cwd }
107136let with_env env t = { t with env }
108137let with_continue_conversation continue t = { t with continue_conversation = continue }
···114143let with_extra_args args t = { t with extra_args = args }
115144let with_debug_stderr sink t = { t with debug_stderr = Some sink }
116145let with_hooks hooks t = { t with hooks = Some hooks }
146146+let with_max_budget_usd budget t = { t with max_budget_usd = Some budget }
147147+let with_fallback_model model t = { t with fallback_model = Some model }
148148+let with_fallback_model_string model t = { t with fallback_model = Some (Model.of_string model) }
149149+let with_setting_sources sources t = { t with setting_sources = Some sources }
150150+let with_no_settings t = { t with setting_sources = Some [] }
151151+let with_max_buffer_size size t = { t with max_buffer_size = Some size }
152152+let with_user user t = { t with user = Some user }
153153+let with_output_format format t = { t with output_format = Some format }
117154118155let to_json t =
119156 let fields = [] in
···145182 | None -> fields
146183 in
147184 let fields = match t.model with
148148- | Some m -> ("model", `String m) :: fields
185185+ | Some m -> ("model", `String (Model.to_string m)) :: fields
149186 | None -> fields
150187 in
151188 let fields =
···182219 try Some (Permissions.Mode.of_json (List.assoc "permission_mode" fields))
183220 with Not_found -> None
184221 in
185185- let model =
186186- try Some (get_string (List.assoc "model" fields))
222222+ let model =
223223+ try Some (Model.of_string (get_string (List.assoc "model" fields)))
187224 with Not_found -> None
188225 in
189226 let env =
···205242 add_dirs = [];
206243 extra_args = [];
207244 debug_stderr = None;
208208- hooks = None; }
245245+ hooks = None;
246246+ max_budget_usd = None;
247247+ fallback_model = None;
248248+ setting_sources = None;
249249+ max_buffer_size = None;
250250+ user = None;
251251+ output_format = None; }
209252 | _ -> raise (Invalid_argument "Options.of_json: expected object")
210253211254let pp fmt t =
···225268 Fmt.(option string) t.system_prompt
226269 Fmt.(option string) t.append_system_prompt
227270 Fmt.(option Permissions.Mode.pp) t.permission_mode
228228- Fmt.(option string) t.model
271271+ Fmt.(option Model.pp) t.model
229272 Fmt.(list (pair string string)) t.env
230273231274let log_options t =
+177-10
claudeio/lib/options.mli
···11(** Configuration options for Claude sessions.
22-33- This module provides configuration options for controlling Claude's
44- behavior, including tool permissions, system prompts, models, and
55- execution environment. *)
22+33+ This module provides comprehensive configuration options for controlling
44+ Claude's behavior, including tool permissions, system prompts, models,
55+ execution environment, cost controls, and structured outputs.
66+77+ {2 Overview}
88+99+ Options control all aspects of Claude's behavior:
1010+ - {b Permissions}: Which tools Claude can use and how permission is granted
1111+ - {b Models}: Which AI model to use and fallback options
1212+ - {b Environment}: Working directory, environment variables, settings
1313+ - {b Cost Control}: Budget limits to prevent runaway spending
1414+ - {b Hooks}: Intercept and modify tool execution
1515+ - {b Structured Output}: JSON schema validation for responses
1616+ - {b Session Management}: Continue or resume conversations
1717+1818+ {2 Builder Pattern}
1919+2020+ Options use a functional builder pattern - each [with_*] function returns
2121+ a new options value with the specified field updated:
2222+2323+ {[
2424+ let options = Options.default
2525+ |> Options.with_model "claude-sonnet-4-5"
2626+ |> Options.with_max_budget_usd 1.0
2727+ |> Options.with_permission_mode Permissions.Mode.Accept_edits
2828+ ]}
2929+3030+ {2 Common Configuration Scenarios}
3131+3232+ {3 CI/CD: Isolated, Reproducible Builds}
3333+3434+ {[
3535+ let ci_config = Options.default
3636+ |> Options.with_no_settings (* Ignore user config *)
3737+ |> Options.with_max_budget_usd 0.50 (* 50 cent limit *)
3838+ |> Options.with_permission_mode
3939+ Permissions.Mode.Bypass_permissions
4040+ |> Options.with_model "claude-haiku-4"
4141+ ]}
4242+4343+ {3 Production: Cost Control with Fallback}
4444+4545+ {[
4646+ let prod_config = Options.default
4747+ |> Options.with_model "claude-sonnet-4-5"
4848+ |> Options.with_fallback_model "claude-haiku-4"
4949+ |> Options.with_max_budget_usd 10.0 (* $10 daily limit *)
5050+ |> Options.with_max_buffer_size 5_000_000
5151+ ]}
5252+5353+ {3 Development: User Settings with Overrides}
5454+5555+ {[
5656+ let dev_config = Options.default
5757+ |> Options.with_setting_sources [User; Project]
5858+ |> Options.with_max_budget_usd 1.0
5959+ |> Options.with_permission_mode Permissions.Mode.Default
6060+ ]}
6161+6262+ {3 Structured Output: Type-Safe Responses}
6363+6464+ {[
6565+ let schema = Ezjsonm.(`O [
6666+ ("type", `String "object");
6767+ ("properties", `O [
6868+ ("count", `O [("type", `String "integer")]);
6969+ ("has_tests", `O [("type", `String "boolean")]);
7070+ ]);
7171+ ])
7272+ let format = Structured_output.of_json_schema schema
7373+7474+ let analysis_config = Options.default
7575+ |> Options.with_output_format format
7676+ |> Options.with_allowed_tools ["Read"; "Glob"; "Grep"]
7777+ ]}
7878+7979+ {2 Advanced Options}
8080+8181+ {3 Budget Control}
8282+8383+ Use {!with_max_budget_usd} to set hard spending limits. Claude will
8484+ terminate the session if the budget is exceeded, preventing runaway costs.
8585+8686+ {3 Settings Isolation}
8787+8888+ Use {!with_setting_sources} or {!with_no_settings} to control which
8989+ configuration files are loaded:
9090+ - [User] - ~/.claude/config
9191+ - [Project] - .claude/ in project root
9292+ - [Local] - Current directory settings
9393+ - [Some \[\]] (via {!with_no_settings}) - No settings, fully isolated
9494+9595+ This is critical for reproducible builds in CI/CD environments.
9696+9797+ {3 Model Fallback}
9898+9999+ Use {!with_fallback_model} to specify an alternative model when the
100100+ primary model is unavailable or overloaded. This improves reliability. *)
61017102open Ezjsonm
81039104(** The log source for options operations *)
10105val src : Logs.Src.t
11106107107+(** {1 Types} *)
108108+109109+type setting_source = User | Project | Local
110110+(** Setting source determines which configuration files to load.
111111+ - [User]: Load user-level settings from ~/.claude/config
112112+ - [Project]: Load project-level settings from .claude/ in project root
113113+ - [Local]: Load local settings from current directory *)
114114+12115type t
13116(** The type of configuration options. *)
14117···27130 ?append_system_prompt:string ->
28131 ?permission_mode:Permissions.Mode.t ->
29132 ?permission_callback:Permissions.callback ->
3030- ?model:string ->
133133+ ?model:Model.t ->
31134 ?cwd:Eio.Fs.dir_ty Eio.Path.t ->
32135 ?env:(string * string) list ->
33136 ?continue_conversation:bool ->
···39142 ?extra_args:(string * string option) list ->
40143 ?debug_stderr:Eio.Flow.sink_ty Eio.Flow.sink ->
41144 ?hooks:Hooks.config ->
145145+ ?max_budget_usd:float ->
146146+ ?fallback_model:Model.t ->
147147+ ?setting_sources:setting_source list ->
148148+ ?max_buffer_size:int ->
149149+ ?user:string ->
150150+ ?output_format:Structured_output.t ->
42151 unit -> t
43152(** [create ?allowed_tools ?disallowed_tools ?max_thinking_tokens ?system_prompt
44153 ?append_system_prompt ?permission_mode ?permission_callback ?model ?cwd ?env
45154 ?continue_conversation ?resume ?max_turns ?permission_prompt_tool_name ?settings
4646- ?add_dirs ?extra_args ?debug_stderr ()]
155155+ ?add_dirs ?extra_args ?debug_stderr ?hooks ?max_budget_usd ?fallback_model
156156+ ?setting_sources ?max_buffer_size ?user ()]
47157 creates a new configuration.
48158 @param allowed_tools List of explicitly allowed tool names
49159 @param disallowed_tools List of explicitly disallowed tool names
···62172 @param settings Path to settings file
63173 @param add_dirs Additional directories to allow access to
64174 @param extra_args Additional CLI flags to pass through
6565- @param debug_stderr Sink for debug output when debug-to-stderr is set *)
175175+ @param debug_stderr Sink for debug output when debug-to-stderr is set
176176+ @param hooks Hooks configuration for event interception
177177+ @param max_budget_usd Hard spending limit in USD (terminates on exceed)
178178+ @param fallback_model Automatic fallback on primary model unavailability
179179+ @param setting_sources Control which settings load (user/project/local)
180180+ @param max_buffer_size Control for stdout buffer size in bytes
181181+ @param user Unix user for subprocess execution
182182+ @param output_format Optional structured output format specification *)
6618367184(** {1 Accessors} *)
68185···87204val permission_callback : t -> Permissions.callback option
88205(** [permission_callback t] returns the optional permission callback. *)
892069090-val model : t -> string option
207207+val model : t -> Model.t option
91208(** [model t] returns the optional model override. *)
9220993210val cwd : t -> Eio.Fs.dir_ty Eio.Path.t option
···123240val hooks : t -> Hooks.config option
124241(** [hooks t] returns the optional hooks configuration. *)
125242243243+val max_budget_usd : t -> float option
244244+(** [max_budget_usd t] returns the optional spending limit in USD. *)
245245+246246+val fallback_model : t -> Model.t option
247247+(** [fallback_model t] returns the optional fallback model. *)
248248+249249+val setting_sources : t -> setting_source list option
250250+(** [setting_sources t] returns the optional list of setting sources to load. *)
251251+252252+val max_buffer_size : t -> int option
253253+(** [max_buffer_size t] returns the optional stdout buffer size in bytes. *)
254254+255255+val user : t -> string option
256256+(** [user t] returns the optional Unix user for subprocess execution. *)
257257+258258+val output_format : t -> Structured_output.t option
259259+(** [output_format t] returns the optional structured output format. *)
260260+126261(** {1 Builders} *)
127262128263val with_allowed_tools : string list -> t -> t
···146281val with_permission_callback : Permissions.callback -> t -> t
147282(** [with_permission_callback callback t] sets the permission callback. *)
148283149149-val with_model : string -> t -> t
150150-(** [with_model model t] sets the model override. *)
284284+val with_model : Model.t -> t -> t
285285+(** [with_model model t] sets the model override using a typed Model.t. *)
286286+287287+val with_model_string : string -> t -> t
288288+(** [with_model_string model t] sets the model override from a string.
289289+ The string is parsed using {!Model.of_string}. *)
151290152291val with_cwd : Eio.Fs.dir_ty Eio.Path.t -> t -> t
153292(** [with_cwd cwd t] sets the working directory. *)
···181320182321val with_hooks : Hooks.config -> t -> t
183322(** [with_hooks hooks t] sets the hooks configuration. *)
323323+324324+val with_max_budget_usd : float -> t -> t
325325+(** [with_max_budget_usd budget t] sets the maximum spending limit in USD.
326326+ The session will terminate if this limit is exceeded. *)
327327+328328+val with_fallback_model : Model.t -> t -> t
329329+(** [with_fallback_model model t] sets the fallback model using a typed Model.t. *)
330330+331331+val with_fallback_model_string : string -> t -> t
332332+(** [with_fallback_model_string model t] sets the fallback model from a string.
333333+ The string is parsed using {!Model.of_string}. *)
334334+335335+val with_setting_sources : setting_source list -> t -> t
336336+(** [with_setting_sources sources t] sets which configuration sources to load.
337337+ Use empty list for isolated environments (e.g., CI/CD). *)
338338+339339+val with_no_settings : t -> t
340340+(** [with_no_settings t] disables all settings loading (user, project, local).
341341+ Useful for CI/CD environments where you want isolated, reproducible behavior. *)
342342+343343+val with_max_buffer_size : int -> t -> t
344344+(** [with_max_buffer_size size t] sets the maximum stdout buffer size in bytes. *)
345345+346346+val with_user : string -> t -> t
347347+(** [with_user user t] sets the Unix user for subprocess execution. *)
348348+349349+val with_output_format : Structured_output.t -> t -> t
350350+(** [with_output_format format t] sets the structured output format. *)
184351185352(** {1 Serialization} *)
186353
+91-4
claudeio/lib/sdk_control.ml
···4040 server_name : string;
4141 message : value;
4242 }
4343-4343+4444+ type set_model = {
4545+ subtype : [`Set_model];
4646+ model : string;
4747+ }
4848+4949+ type get_server_info = {
5050+ subtype : [`Get_server_info];
5151+ }
5252+4453 type t =
4554 | Interrupt of interrupt
4655 | Permission of permission
···4857 | Set_permission_mode of set_permission_mode
4958 | Hook_callback of hook_callback
5059 | Mcp_message of mcp_message
6060+ | Set_model of set_model
6161+ | Get_server_info of get_server_info
51625263 let interrupt () = Interrupt { subtype = `Interrupt }
5364···8091 server_name;
8192 message;
8293 }
8383-9494+9595+ let set_model ~model =
9696+ Set_model { subtype = `Set_model; model }
9797+9898+ let get_server_info () =
9999+ Get_server_info { subtype = `Get_server_info }
100100+84101 let to_json = function
85102 | Interrupt _ ->
86103 `O [("subtype", `String "interrupt")]
···131148 ("server_name", `String m.server_name);
132149 ("message", m.message);
133150 ]
134134-151151+ | Set_model s ->
152152+ `O [
153153+ ("subtype", `String "set_model");
154154+ ("model", `String s.model);
155155+ ]
156156+ | Get_server_info _ ->
157157+ `O [("subtype", `String "get_server_info")]
158158+135159 let of_json = function
136160 | `O fields ->
137161 let subtype = JU.assoc_string "subtype" fields in
···183207 server_name;
184208 message;
185209 }
210210+ | "set_model" ->
211211+ let model = JU.assoc_string "model" fields in
212212+ Set_model { subtype = `Set_model; model }
213213+ | "get_server_info" ->
214214+ Get_server_info { subtype = `Get_server_info }
186215 | _ -> raise (Invalid_argument ("Unknown request subtype: " ^ subtype)))
187216 | _ -> raise (Invalid_argument "Request.of_json: expected object")
188217···204233 | Mcp_message m ->
205234 Fmt.pf fmt "@[<2>McpMessage@ { server = %S }@]"
206235 m.server_name
236236+ | Set_model s ->
237237+ Fmt.pf fmt "@[<2>SetModel@ { model = %S }@]" s.model
238238+ | Get_server_info _ ->
239239+ Fmt.pf fmt "@[<2>GetServerInfo@]"
207240end
208241209242module Response = struct
···360393 Log.debug (fun m -> m "SDK control request: %a" Request.pp req)
361394362395let log_response resp =
363363- Log.debug (fun m -> m "SDK control response: %a" Response.pp resp)396396+ Log.debug (fun m -> m "SDK control response: %a" Response.pp resp)
397397+398398+(** Server information *)
399399+module Server_info = struct
400400+ type t = {
401401+ version : string;
402402+ capabilities : string list;
403403+ commands : string list;
404404+ output_styles : string list;
405405+ }
406406+407407+ let create ~version ~capabilities ~commands ~output_styles =
408408+ { version; capabilities; commands; output_styles }
409409+410410+ let version t = t.version
411411+ let capabilities t = t.capabilities
412412+ let commands t = t.commands
413413+ let output_styles t = t.output_styles
414414+415415+ let of_json = function
416416+ | `O fields ->
417417+ let version = JU.assoc_string "version" fields in
418418+ let capabilities =
419419+ match List.assoc_opt "capabilities" fields with
420420+ | Some (`A lst) -> List.map Ezjsonm.get_string lst
421421+ | _ -> []
422422+ in
423423+ let commands =
424424+ match List.assoc_opt "commands" fields with
425425+ | Some (`A lst) -> List.map Ezjsonm.get_string lst
426426+ | _ -> []
427427+ in
428428+ let output_styles =
429429+ match List.assoc_opt "outputStyles" fields with
430430+ | Some (`A lst) -> List.map Ezjsonm.get_string lst
431431+ | _ -> []
432432+ in
433433+ { version; capabilities; commands; output_styles }
434434+ | _ -> raise (Invalid_argument "Server_info.of_json: expected object")
435435+436436+ let to_json t =
437437+ `O [
438438+ ("version", `String t.version);
439439+ ("capabilities", `A (List.map (fun s -> `String s) t.capabilities));
440440+ ("commands", `A (List.map (fun s -> `String s) t.commands));
441441+ ("outputStyles", `A (List.map (fun s -> `String s) t.output_styles));
442442+ ]
443443+444444+ let pp fmt t =
445445+ Fmt.pf fmt "@[<2>ServerInfo@ { version = %S;@ capabilities = [%a];@ commands = [%a];@ output_styles = [%a] }@]"
446446+ t.version
447447+ Fmt.(list ~sep:(any ", ") (quote string)) t.capabilities
448448+ Fmt.(list ~sep:(any ", ") (quote string)) t.commands
449449+ Fmt.(list ~sep:(any ", ") (quote string)) t.output_styles
450450+end
+149-6
claudeio/lib/sdk_control.mli
···11(** SDK Control Protocol for Claude.
22-33- This module defines the typed SDK control protocol for communication
44- between the SDK and Claude, including request and response types. *)
22+33+ This module defines the typed SDK control protocol for bidirectional
44+ communication between the SDK and the Claude CLI. It handles:
55+66+ - Permission requests (tool usage authorization)
77+ - Hook callbacks (intercepting and modifying tool execution)
88+ - Dynamic control (changing settings mid-conversation)
99+ - Server introspection (querying capabilities)
1010+1111+ {2 Protocol Overview}
1212+1313+ The SDK control protocol is a JSON-based request/response protocol that
1414+ runs alongside the main message stream. It enables:
1515+1616+ 1. {b Callbacks}: Claude asks the SDK for permission or hook execution
1717+ 2. {b Control}: SDK changes Claude's behavior dynamically
1818+ 3. {b Introspection}: SDK queries server metadata
1919+2020+ {2 Request/Response Flow}
2121+2222+ {v
2323+ SDK Claude CLI
2424+ | |
2525+ |-- Initialize (with hooks) --> |
2626+ |<-- Permission Request --------| (for tool usage)
2727+ |-- Allow/Deny Response ------> |
2828+ | |
2929+ |<-- Hook Callback -------------| (pre/post tool)
3030+ |-- Hook Result -------------> |
3131+ | |
3232+ |-- Set Model ---------------> | (dynamic control)
3333+ |<-- Success Response ----------|
3434+ | |
3535+ |-- Get Server Info ----------> |
3636+ |<-- Server Info Response ------|
3737+ v}
3838+3939+ {2 Usage}
4040+4141+ Most users won't interact with this module directly. The {!Client} module
4242+ handles the protocol automatically. However, this module is exposed for:
4343+4444+ - Understanding the control protocol
4545+ - Implementing custom control logic
4646+ - Debugging control message flow
4747+ - Advanced SDK extensions
4848+4949+ {2 Dynamic Control Examples}
5050+5151+ See {!Client.set_permission_mode}, {!Client.set_model}, and
5252+ {!Client.get_server_info} for high-level APIs that use this protocol. *)
553654open Ezjsonm
755···53101 message : value;
54102 }
55103 (** MCP server message request. *)
5656-104104+105105+ type set_model = {
106106+ subtype : [`Set_model];
107107+ model : string;
108108+ }
109109+ (** Request to change the AI model. *)
110110+111111+ type get_server_info = {
112112+ subtype : [`Get_server_info];
113113+ }
114114+ (** Request to get server information. *)
115115+57116 type t =
58117 | Interrupt of interrupt
59118 | Permission of permission
···61120 | Set_permission_mode of set_permission_mode
62121 | Hook_callback of hook_callback
63122 | Mcp_message of mcp_message
123123+ | Set_model of set_model
124124+ | Get_server_info of get_server_info
64125 (** The type of SDK control requests. *)
6512666127 val interrupt : unit -> t
···9015191152 val mcp_message : server_name:string -> message:value -> t
92153 (** [mcp_message ~server_name ~message] creates an MCP message request. *)
9393-154154+155155+ val set_model : model:string -> t
156156+ (** [set_model ~model] creates a model change request. *)
157157+158158+ val get_server_info : unit -> t
159159+ (** [get_server_info ()] creates a server info request. *)
160160+94161 val to_json : t -> value
95162 (** [to_json t] converts a request to JSON. *)
96163···185252(** [log_request req] logs an SDK control request. *)
186253187254val log_response : Response.t -> unit
188188-(** [log_response resp] logs an SDK control response. *)255255+(** [log_response resp] logs an SDK control response. *)
256256+257257+(** {1 Server Information}
258258+259259+ Server information provides metadata about the Claude CLI server,
260260+ including version, capabilities, available commands, and output styles.
261261+262262+ {2 Use Cases}
263263+264264+ - Feature detection: Check if specific capabilities are available
265265+ - Version compatibility: Ensure minimum version requirements
266266+ - Debugging: Log server information for troubleshooting
267267+ - Dynamic adaptation: Adjust SDK behavior based on capabilities
268268+269269+ {2 Example}
270270+271271+ {[
272272+ let info = Client.get_server_info client in
273273+ Printf.printf "Claude CLI version: %s\n"
274274+ (Server_info.version info);
275275+276276+ if List.mem "structured-output" (Server_info.capabilities info) then
277277+ Printf.printf "Structured output is supported\n"
278278+ else
279279+ Printf.printf "Structured output not available\n";
280280+ ]} *)
281281+282282+module Server_info : sig
283283+ (** Server information and capabilities. *)
284284+285285+ type t = {
286286+ version : string;
287287+ (** Server version string (e.g., "2.0.0") *)
288288+289289+ capabilities : string list;
290290+ (** Available server capabilities (e.g., "hooks", "structured-output") *)
291291+292292+ commands : string list;
293293+ (** Available CLI commands *)
294294+295295+ output_styles : string list;
296296+ (** Supported output formats (e.g., "json", "stream-json") *)
297297+ }
298298+ (** Server metadata and capabilities.
299299+300300+ This information is useful for feature detection and debugging. *)
301301+302302+ val create :
303303+ version:string ->
304304+ capabilities:string list ->
305305+ commands:string list ->
306306+ output_styles:string list ->
307307+ t
308308+ (** [create ~version ~capabilities ~commands ~output_styles] creates server info. *)
309309+310310+ val version : t -> string
311311+ (** [version t] returns the server version. *)
312312+313313+ val capabilities : t -> string list
314314+ (** [capabilities t] returns the server capabilities. *)
315315+316316+ val commands : t -> string list
317317+ (** [commands t] returns available commands. *)
318318+319319+ val output_styles : t -> string list
320320+ (** [output_styles t] returns available output styles. *)
321321+322322+ val of_json : value -> t
323323+ (** [of_json json] parses server info from JSON.
324324+ @raise Invalid_argument if the JSON is not valid server info. *)
325325+326326+ val to_json : t -> value
327327+ (** [to_json t] converts server info to JSON. *)
328328+329329+ val pp : Format.formatter -> t -> unit
330330+ (** [pp fmt t] pretty-prints the server info. *)
331331+end
+45-9
claudeio/lib/transport.ml
···1717 sw : Switch.t;
1818}
19192020+let setting_source_to_string = function
2121+ | Options.User -> "user"
2222+ | Options.Project -> "project"
2323+ | Options.Local -> "local"
2424+2025let build_command ~claude_path ~options =
2126 let cmd = [claude_path; "--output-format"; "stream-json"; "--verbose"] in
2222-2727+2328 let cmd = match Options.system_prompt options with
2429 | Some prompt -> cmd @ ["--system-prompt"; prompt]
2530 | None -> cmd
2631 in
2727-3232+2833 let cmd = match Options.append_system_prompt options with
2934 | Some prompt -> cmd @ ["--append-system-prompt"; prompt]
3035 | None -> cmd
3136 in
3232-3737+3338 let cmd = match Options.allowed_tools options with
3439 | [] -> cmd
3540 | tools -> cmd @ ["--allowedTools"; String.concat "," tools]
3641 in
3737-4242+3843 let cmd = match Options.disallowed_tools options with
3944 | [] -> cmd
4045 | tools -> cmd @ ["--disallowedTools"; String.concat "," tools]
4146 in
4242-4747+4348 let cmd = match Options.model options with
4444- | Some model -> cmd @ ["--model"; model]
4949+ | Some model -> cmd @ ["--model"; Model.to_string model]
4550 | None -> cmd
4651 in
4747-5252+4853 let cmd = match Options.permission_mode options with
4954 | Some mode ->
5055 let mode_str = Permissions.Mode.to_string mode in
···5762 | None -> cmd
5863 in
59646565+ (* Advanced configuration options *)
6666+ let cmd = match Options.max_budget_usd options with
6767+ | Some budget -> cmd @ ["--max-budget-usd"; Float.to_string budget]
6868+ | None -> cmd
6969+ in
7070+7171+ let cmd = match Options.fallback_model options with
7272+ | Some model -> cmd @ ["--fallback-model"; Model.to_string model]
7373+ | None -> cmd
7474+ in
7575+7676+ let cmd = match Options.setting_sources options with
7777+ | Some sources ->
7878+ let sources_str = String.concat "," (List.map setting_source_to_string sources) in
7979+ cmd @ ["--setting-sources"; sources_str]
8080+ | None -> cmd
8181+ in
8282+8383+ (* Add JSON Schema if specified *)
8484+ let cmd = match Options.output_format options with
8585+ | Some format ->
8686+ let schema = Structured_output.json_schema format in
8787+ let schema_str = Ezjsonm.value_to_string schema in
8888+ cmd @ ["--json-schema"; schema_str]
8989+ | None -> cmd
9090+ in
9191+6092 (* Use streaming input mode *)
6193 cmd @ ["--input-format"; "stream-json"]
6294···121153122154 let stdin = (stdin_w :> Eio.Flow.sink_ty r) in
123155 let stdin_close = (stdin_w :> [`Close | `Flow] r) in
124124- let stdout = Eio.Buf_read.of_flow ~max_size:1_000_000 (stdout_r :> Eio.Flow.source_ty r) in
125125-156156+ let max_size = match Options.max_buffer_size options with
157157+ | Some size -> size
158158+ | None -> 1_000_000 (* Default 1MB *)
159159+ in
160160+ let stdout = Eio.Buf_read.of_flow ~max_size (stdout_r :> Eio.Flow.source_ty r) in
161161+126162 { process = P process; stdin; stdin_close; stdout; sw }
127163128164let send t json =
+1-1
claudeio/test/camel_jokes.ml
···45454646let run_claude ~sw ~env name prompt =
4747 Log.info (fun m -> m "🐪 Starting %s..." name);
4848- let options = Claude.Options.create ~model:"sonnet" ~allowed_tools:[] () in
4848+ let options = Claude.Options.create ~model:(Claude.Model.of_string "sonnet") ~allowed_tools:[] () in
49495050 let client = Claude.Client.create ~options ~sw
5151 ~process_mgr:env#process_mgr
+1-1
claudeio/test/discovery_demo.ml
···3737 Log.app (fun m -> m "This will discover what permissions Claude needs.\n");
38383939 (* Create client with discovery mode *)
4040- let options = Claude.Options.create ~model:"sonnet" () in
4040+ let options = Claude.Options.create ~model:(Claude.Model.of_string "sonnet") () in
4141 let client = Claude.Client.discover_permissions
4242 (Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ()) in
4343
···5151 in
52525353 let options = Claude.Options.create
5454- ~model:"sonnet"
5454+ ~model:(Claude.Model.of_string "sonnet")
5555 ~hooks
5656 () in
5757
+1-1
claudeio/test/permission_demo.ml
···131131 (* DON'T specify allowed_tools - let the permission callback handle everything.
132132 The Default permission mode with a callback should send requests for all tools. *)
133133 let options = Claude.Options.create
134134- ~model:"sonnet"
134134+ ~model:(Claude.Model.of_string "sonnet")
135135 ~permission_mode:Claude.Permissions.Mode.Default
136136 ~permission_callback:interactive_permission_callback
137137 () in
+1-1
claudeio/test/simple_permission_test.ml
···17171818 (* Create options with permission callback *)
1919 let options = Claude.Options.create
2020- ~model:"sonnet"
2020+ ~model:(Claude.Model.of_string "sonnet")
2121 ~permission_callback:auto_allow_callback
2222 () in
2323
+1-1
claudeio/test/test_permissions.ml
···14141515 (* Create options with custom permission callback *)
1616 let options = Claude.Options.create
1717- ~model:"sonnet"
1717+ ~model:(Claude.Model.of_string "sonnet")
1818 ~permission_callback:auto_allow_callback
1919 () in
2020