···99module Structured_output = Structured_output
1010module Options = Options
1111module Transport = Transport
1212-module Client = Client1212+module Client = Client
+57-54
lib/claude.mli
···17171818 The library is structured into several focused modules:
19192020- - {!Content_block}: Defines content blocks (text, tool use, tool results, thinking)
2121- - {!Message}: Messages exchanged with Claude (user, assistant, system, result)
2020+ - {!Content_block}: Defines content blocks (text, tool use, tool results,
2121+ thinking)
2222+ - {!Message}: Messages exchanged with Claude (user, assistant, system,
2323+ result)
2224 - {!Control}: Control flow messages for session management
2325 - {!Permissions}: Fine-grained permission system for tool usage
2426 - {!Options}: Configuration options for Claude sessions
···29313032 {[
3133 open Claude
3232-3434+3335 (* Create a simple query *)
3436 let query_claude ~sw env prompt =
3537 let options = Options.default in
3638 Client.query ~sw env ~options prompt
3737-3939+3840 (* Process streaming responses *)
3941 let process_response messages =
4040- Seq.iter (function
4141- | Message.Assistant msg ->
4242- List.iter (function
4343- | Content_block.Text t ->
4444- print_endline (Content_block.Text.text t)
4545- | _ -> ()
4646- ) (Message.Assistant.content msg)
4747- | _ -> ()
4848- ) messages
4242+ Seq.iter
4343+ (function
4444+ | Message.Assistant msg ->
4545+ List.iter
4646+ (function
4747+ | Content_block.Text t ->
4848+ print_endline (Content_block.Text.text t)
4949+ | _ -> ())
5050+ (Message.Assistant.content msg)
5151+ | _ -> ())
5252+ messages
4953 ]}
50545155 {1 Advanced Features}
···5559 Control which tools Claude can use and how:
56605761 {[
5858- let options =
6262+ let options =
5963 Options.default
6060- |> Options.with_allowed_tools ["Read"; "Write"; "Bash"]
6464+ |> Options.with_allowed_tools [ "Read"; "Write"; "Bash" ]
6165 |> Options.with_permission_mode Permissions.Mode.Accept_edits
6266 ]}
6367···6973 let my_callback ~tool_name ~input ~context =
7074 if tool_name = "Bash" then
7175 Permissions.Result.deny ~message:"Bash not allowed" ~interrupt:false
7272- else
7373- Permissions.Result.allow ()
7474-7575- let options = Options.default |> Options.with_permission_callback my_callback
7676+ else Permissions.Result.allow ()
7777+7878+ let options =
7979+ Options.default |> Options.with_permission_callback my_callback
7680 ]}
77817882 {2 System Prompts}
···8084 Customize Claude's behavior with system prompts:
81858286 {[
8383- let options =
8787+ let options =
8488 Options.default
8585- |> Options.with_system_prompt "You are a helpful OCaml programming assistant."
8989+ |> Options.with_system_prompt
9090+ "You are a helpful OCaml programming assistant."
8691 |> Options.with_append_system_prompt "Always use Jane Street style."
8792 ]}
88938994 {1 Logging}
90959191- The library uses the Logs library for structured logging. Each module has its
9292- own log source (e.g., "claude.message", "claude.transport") allowing fine-grained
9393- control over logging verbosity:
9696+ The library uses the Logs library for structured logging. Each module has
9797+ its own log source (e.g., "claude.message", "claude.transport") allowing
9898+ fine-grained control over logging verbosity:
949995100 {[
96101 (* Enable debug logging for message handling *)
97102 Logs.Src.set_level Message.src (Some Logs.Debug);
9898-103103+99104 (* Enable info logging for transport layer *)
100100- Logs.Src.set_level Transport.src (Some Logs.Info);
105105+ Logs.Src.set_level Transport.src (Some Logs.Info)
101106 ]}
102107103108 {1 Error Handling}
···111116112117 {[
113118 let run_claude_session ~sw env =
114114- let options =
115115- Options.create
116116- ~allowed_tools:["Read"; "Write"]
119119+ let options =
120120+ Options.create ~allowed_tools:[ "Read"; "Write" ]
117121 ~permission_mode:Permissions.Mode.Accept_edits
118118- ~system_prompt:"You are an OCaml expert."
119119- ~max_thinking_tokens:10000
122122+ ~system_prompt:"You are an OCaml expert." ~max_thinking_tokens:10000
120123 ()
121124 in
122122-125125+123126 let prompt = "Write a function to calculate fibonacci numbers" in
124127 let messages = Client.query ~sw env ~options prompt in
125125-126126- Seq.iter (fun msg ->
127127- Message.log_received msg;
128128- match msg with
129129- | Message.Assistant assistant ->
130130- Printf.printf "Claude: %s\n"
131131- (Message.Assistant.model assistant);
132132- List.iter (function
133133- | Content_block.Text t ->
134134- print_endline (Content_block.Text.text t)
135135- | Content_block.Tool_use t ->
136136- Printf.printf "Using tool: %s\n"
137137- (Content_block.Tool_use.name t)
138138- | _ -> ()
139139- ) (Message.Assistant.content assistant)
140140- | Message.Result result ->
141141- Printf.printf "Session complete. Duration: %dms\n"
142142- (Message.Result.duration_ms result)
143143- | _ -> ()
144144- ) messages
145145- ]}
146146-*)
128128+129129+ Seq.iter
130130+ (fun msg ->
131131+ Message.log_received msg;
132132+ match msg with
133133+ | Message.Assistant assistant ->
134134+ Printf.printf "Claude: %s\n" (Message.Assistant.model assistant);
135135+ List.iter
136136+ (function
137137+ | Content_block.Text t ->
138138+ print_endline (Content_block.Text.text t)
139139+ | Content_block.Tool_use t ->
140140+ Printf.printf "Using tool: %s\n"
141141+ (Content_block.Tool_use.name t)
142142+ | _ -> ())
143143+ (Message.Assistant.content assistant)
144144+ | Message.Result result ->
145145+ Printf.printf "Session complete. Duration: %dms\n"
146146+ (Message.Result.duration_ms result)
147147+ | _ -> ())
148148+ messages
149149+ ]} *)
147150148151(** {1 Modules} *)
149152
+218-177
lib/client.ml
···11let src = Logs.Src.create "claude.client" ~doc:"Claude client"
22+23module Log = (val Logs.src_log src : Logs.LOG)
3445(** Control response builders using Sdk_control codecs *)
···67 let success ~request_id ~response =
78 let resp = Sdk_control.Response.success ~request_id ?response () in
89 let ctrl = Sdk_control.create_response ~response:resp () in
99- match Jsont.Json.encode Sdk_control.jsont ctrl with
1010- | Ok json -> json
1111- | Error msg -> failwith ("Control_response.success: " ^ msg)
1010+ Jsont.Json.encode Sdk_control.jsont ctrl
1111+ |> Err.get_ok ~msg:"Control_response.success: "
12121313 let error ~request_id ~message =
1414 let resp = Sdk_control.Response.error ~request_id ~error:message () in
1515 let ctrl = Sdk_control.create_response ~response:resp () in
1616- match Jsont.Json.encode Sdk_control.jsont ctrl with
1717- | Ok json -> json
1818- | Error msg -> failwith ("Control_response.error: " ^ msg)
1616+ Jsont.Json.encode Sdk_control.jsont ctrl
1717+ |> Err.get_ok ~msg:"Control_response.error: "
1918end
20192120(* Helper functions for JSON manipulation using jsont *)
2221let json_to_string json =
2323- match Jsont_bytesrw.encode_string' Jsont.json json with
2424- | Ok s -> s
2525- | Error err -> failwith (Jsont.Error.to_string err)
2222+ Jsont_bytesrw.encode_string' Jsont.json json
2323+ |> Result.map_error Jsont.Error.to_string
2424+ |> Err.get_ok ~msg:""
26252727-(** Wire-level codec for permission responses to CLI.
2828- Uses camelCase field names as expected by the CLI protocol. *)
2626+(** Wire-level codec for permission responses to CLI. Uses camelCase field names
2727+ as expected by the CLI protocol. *)
2928module Permission_wire = struct
3029 type allow = { allow_behavior : string; allow_updated_input : Jsont.json }
3130 type deny = { deny_behavior : string; deny_message : string }
32313332 let allow_jsont : allow Jsont.t =
3434- let make allow_behavior allow_updated_input = { allow_behavior; allow_updated_input } in
3333+ let make allow_behavior allow_updated_input =
3434+ { allow_behavior; allow_updated_input }
3535+ in
3536 Jsont.Object.map ~kind:"AllowWire" make
3637 |> Jsont.Object.mem "behavior" Jsont.string ~enc:(fun r -> r.allow_behavior)
3737- |> Jsont.Object.mem "updatedInput" Jsont.json ~enc:(fun r -> r.allow_updated_input)
3838+ |> Jsont.Object.mem "updatedInput" Jsont.json ~enc:(fun r ->
3939+ r.allow_updated_input)
3840 |> Jsont.Object.finish
39414042 let deny_jsont : deny Jsont.t =
···4547 |> Jsont.Object.finish
46484749 let encode_allow ~updated_input =
4848- match Jsont.Json.encode allow_jsont { allow_behavior = "allow"; allow_updated_input = updated_input } with
4949- | Ok json -> json
5050- | Error msg -> failwith ("Permission_wire.encode_allow: " ^ msg)
5050+ Jsont.Json.encode allow_jsont
5151+ { allow_behavior = "allow"; allow_updated_input = updated_input }
5252+ |> Err.get_ok ~msg:"Permission_wire.encode_allow: "
51535254 let encode_deny ~message =
5353- match Jsont.Json.encode deny_jsont { deny_behavior = "deny"; deny_message = message } with
5454- | Ok json -> json
5555- | Error msg -> failwith ("Permission_wire.encode_deny: " ^ msg)
5555+ Jsont.Json.encode deny_jsont
5656+ { deny_behavior = "deny"; deny_message = message }
5757+ |> Err.get_ok ~msg:"Permission_wire.encode_deny: "
5658end
57595860(** Wire-level codec for hook matcher configuration sent to CLI. *)
5961module Hook_matcher_wire = struct
6060- type t = {
6161- matcher : string option;
6262- hook_callback_ids : string list;
6363- }
6262+ type t = { matcher : string option; hook_callback_ids : string list }
64636564 let jsont : t Jsont.t =
6665 let make matcher hook_callback_ids = { matcher; hook_callback_ids } in
6766 Jsont.Object.map ~kind:"HookMatcherWire" make
6867 |> Jsont.Object.opt_mem "matcher" Jsont.string ~enc:(fun r -> r.matcher)
6969- |> Jsont.Object.mem "hookCallbackIds" (Jsont.list Jsont.string) ~enc:(fun r -> r.hook_callback_ids)
6868+ |> Jsont.Object.mem "hookCallbackIds" (Jsont.list Jsont.string)
6969+ ~enc:(fun r -> r.hook_callback_ids)
7070 |> Jsont.Object.finish
71717272 let encode matchers =
7373- Jsont.Json.list (List.map (fun m ->
7474- match Jsont.Json.encode jsont m with
7575- | Ok json -> json
7676- | Error msg -> failwith ("Hook_matcher_wire.encode: " ^ msg)
7777- ) matchers)
7373+ List.map
7474+ (fun m ->
7575+ Jsont.Json.encode jsont m
7676+ |> Err.get_ok ~msg:"Hook_matcher_wire.encode: ")
7777+ matchers
7878+ |> Jsont.Json.list
7879end
79808081type t = {
···92939394let handle_control_request t (ctrl_req : Incoming.Control_request.t) =
9495 let request_id = Incoming.Control_request.request_id ctrl_req in
9595- Log.info (fun m -> m "Handling control request: %s" (Incoming.Control_request.subtype ctrl_req));
9696+ Log.info (fun m ->
9797+ m "Handling control request: %s"
9898+ (Incoming.Control_request.subtype ctrl_req));
969997100 match Incoming.Control_request.request ctrl_req with
98101 | Incoming.Control_request.Can_use_tool req ->
99102 let tool_name = Incoming.Control_request.Can_use_tool.tool_name req in
100103 let input = Incoming.Control_request.Can_use_tool.input req in
101101- Log.info (fun m -> m "Permission request for tool '%s' with input: %s"
102102- tool_name (json_to_string input));
104104+ Log.info (fun m ->
105105+ m "Permission request for tool '%s' with input: %s" tool_name
106106+ (json_to_string input));
103107 (* TODO: Parse permission_suggestions properly *)
104108 let context = Permissions.Context.create ~suggestions:[] () in
105109106106- Log.info (fun m -> m "Invoking permission callback for tool: %s" tool_name);
107107- let result = match t.permission_callback with
108108- | Some callback ->
109109- Log.info (fun m -> m "Using custom permission callback");
110110- callback ~tool_name ~input ~context
111111- | None ->
112112- Log.info (fun m -> m "Using default allow callback");
113113- Permissions.default_allow_callback ~tool_name ~input ~context
110110+ Log.info (fun m ->
111111+ m "Invoking permission callback for tool: %s" tool_name);
112112+ let callback =
113113+ Option.value t.permission_callback
114114+ ~default:Permissions.default_allow_callback
114115 in
115115- Log.info (fun m -> m "Permission callback returned: %s"
116116- (match result with
117117- | Permissions.Result.Allow _ -> "ALLOW"
118118- | Permissions.Result.Deny _ -> "DENY"));
116116+ let result = callback ~tool_name ~input ~context in
117117+ Log.info (fun m ->
118118+ m "Permission callback returned: %s"
119119+ (match result with
120120+ | Permissions.Result.Allow _ -> "ALLOW"
121121+ | Permissions.Result.Deny _ -> "DENY"));
119122120123 (* Convert permission result to CLI format using wire codec *)
121121- let response_data = match result with
122122- | Permissions.Result.Allow { updated_input; updated_permissions = _; unknown = _ } ->
124124+ let response_data =
125125+ match result with
126126+ | Permissions.Result.Allow
127127+ { updated_input; updated_permissions = _; unknown = _ } ->
123128 let updated_input = Option.value updated_input ~default:input in
124129 Permission_wire.encode_allow ~updated_input
125130 | Permissions.Result.Deny { message; interrupt = _; unknown = _ } ->
126131 Permission_wire.encode_deny ~message
127132 in
128128- let response = Control_response.success ~request_id ~response:(Some response_data) in
129129- Log.info (fun m -> m "Sending control response: %s" (json_to_string response));
133133+ let response =
134134+ Control_response.success ~request_id ~response:(Some response_data)
135135+ in
136136+ Log.info (fun m ->
137137+ m "Sending control response: %s" (json_to_string response));
130138 Transport.send t.transport response
131131-132132- | Incoming.Control_request.Hook_callback req ->
133133- let callback_id = Incoming.Control_request.Hook_callback.callback_id req in
139139+ | Incoming.Control_request.Hook_callback req -> (
140140+ let callback_id =
141141+ Incoming.Control_request.Hook_callback.callback_id req
142142+ in
134143 let input = Incoming.Control_request.Hook_callback.input req in
135135- let tool_use_id = Incoming.Control_request.Hook_callback.tool_use_id req in
136136- Log.info (fun m -> m "Hook callback request for callback_id: %s" callback_id);
144144+ let tool_use_id =
145145+ Incoming.Control_request.Hook_callback.tool_use_id req
146146+ in
147147+ Log.info (fun m ->
148148+ m "Hook callback request for callback_id: %s" callback_id);
137149138138- (try
150150+ try
139151 let callback = Hashtbl.find t.hook_callbacks callback_id in
140152 let context = Hooks.Context.create () in
141153 let result = callback ~input ~tool_use_id ~context in
142154143143- let result_json = match Jsont.Json.encode Hooks.result_jsont result with
144144- | Ok j -> j
145145- | Error msg -> failwith ("Failed to encode hook result: " ^ msg)
155155+ let result_json =
156156+ Jsont.Json.encode Hooks.result_jsont result
157157+ |> Err.get_ok ~msg:"Failed to encode hook result: "
146158 in
147147- let response = Control_response.success ~request_id ~response:(Some result_json) in
159159+ let response =
160160+ Control_response.success ~request_id ~response:(Some result_json)
161161+ in
148162 Log.info (fun m -> m "Hook callback succeeded, sending response");
149163 Transport.send t.transport response
150164 with
151165 | Not_found ->
152152- let error_msg = Printf.sprintf "Hook callback not found: %s" callback_id in
166166+ let error_msg =
167167+ Printf.sprintf "Hook callback not found: %s" callback_id
168168+ in
153169 Log.err (fun m -> m "%s" error_msg);
154154- Transport.send t.transport (Control_response.error ~request_id ~message:error_msg)
170170+ Transport.send t.transport
171171+ (Control_response.error ~request_id ~message:error_msg)
155172 | exn ->
156156- let error_msg = Printf.sprintf "Hook callback error: %s" (Printexc.to_string exn) in
173173+ let error_msg =
174174+ Printf.sprintf "Hook callback error: %s" (Printexc.to_string exn)
175175+ in
157176 Log.err (fun m -> m "%s" error_msg);
158158- Transport.send t.transport (Control_response.error ~request_id ~message:error_msg))
159159-177177+ Transport.send t.transport
178178+ (Control_response.error ~request_id ~message:error_msg))
160179 | Incoming.Control_request.Unknown (subtype, _) ->
161161- let error_msg = Printf.sprintf "Unsupported control request: %s" subtype in
162162- Transport.send t.transport (Control_response.error ~request_id ~message:error_msg)
180180+ let error_msg =
181181+ Printf.sprintf "Unsupported control request: %s" subtype
182182+ in
183183+ Transport.send t.transport
184184+ (Control_response.error ~request_id ~message:error_msg)
163185164186let handle_control_response t control_resp =
165165- let request_id = match control_resp.Sdk_control.response with
187187+ let request_id =
188188+ match control_resp.Sdk_control.response with
166189 | Sdk_control.Response.Success s -> s.request_id
167190 | Sdk_control.Response.Error e -> e.request_id
168191 in
169169- Log.debug (fun m -> m "Received control response for request_id: %s" request_id);
192192+ Log.debug (fun m ->
193193+ m "Received control response for request_id: %s" request_id);
170194171195 (* Store the response as JSON and signal waiting threads *)
172172- let json = match Jsont.Json.encode Sdk_control.control_response_jsont control_resp with
173173- | Ok j -> j
174174- | Error err -> failwith ("Failed to encode control response: " ^ err)
196196+ let json =
197197+ Jsont.Json.encode Sdk_control.control_response_jsont control_resp
198198+ |> Err.get_ok ~msg:"Failed to encode control response: "
175199 in
176200 Eio.Mutex.use_rw ~protect:false t.control_mutex (fun () ->
177177- Hashtbl.replace t.control_responses request_id json;
178178- Eio.Condition.broadcast t.control_condition
179179- )
201201+ Hashtbl.replace t.control_responses request_id json;
202202+ Eio.Condition.broadcast t.control_condition)
180203181204let handle_messages t =
182205 let rec loop () =
···185208 (* EOF *)
186209 Log.debug (fun m -> m "Handle messages: EOF received");
187210 Seq.Nil
188188- | Some line ->
211211+ | Some line -> (
189212 (* Use unified Incoming codec for all message types *)
190213 match Jsont_bytesrw.decode_string' Incoming.jsont line with
191214 | Ok (Incoming.Message msg) ->
···194217 (* Extract session ID from system messages *)
195218 (match msg with
196219 | Message.System sys ->
197197- (match Message.System.session_id sys with
198198- | Some session_id ->
199199- t.session_id <- Some session_id;
200200- Log.debug (fun m -> m "Stored session ID: %s" session_id)
201201- | None -> ())
220220+ Message.System.session_id sys
221221+ |> Option.iter (fun session_id ->
222222+ t.session_id <- Some session_id;
223223+ Log.debug (fun m -> m "Stored session ID: %s" session_id))
202224 | _ -> ());
203225204226 Seq.Cons (msg, loop)
205205-206227 | Ok (Incoming.Control_response resp) ->
207228 handle_control_response t resp;
208229 loop ()
209209-210230 | Ok (Incoming.Control_request ctrl_req) ->
211211- Log.info (fun m -> m "Received control request: %s (request_id: %s)"
212212- (Incoming.Control_request.subtype ctrl_req)
213213- (Incoming.Control_request.request_id ctrl_req));
231231+ Log.info (fun m ->
232232+ m "Received control request: %s (request_id: %s)"
233233+ (Incoming.Control_request.subtype ctrl_req)
234234+ (Incoming.Control_request.request_id ctrl_req));
214235 handle_control_request t ctrl_req;
215236 loop ()
216216-217237 | Error err ->
218218- Log.err (fun m -> m "Failed to decode incoming message: %s\nLine: %s"
219219- (Jsont.Error.to_string err) line);
220220- loop ()
238238+ Log.err (fun m ->
239239+ m "Failed to decode incoming message: %s\nLine: %s"
240240+ (Jsont.Error.to_string err)
241241+ line);
242242+ loop ())
221243 in
222244 Log.debug (fun m -> m "Starting message handler");
223245 loop
···238260 let hook_callbacks = Hashtbl.create 16 in
239261 let next_callback_id = ref 0 in
240262241241- let t = {
242242- transport;
243243- permission_callback = Options.permission_callback options;
244244- permission_log = None;
245245- hook_callbacks;
246246- session_id = None;
247247- control_responses = Hashtbl.create 16;
248248- control_mutex = Eio.Mutex.create ();
249249- control_condition = Eio.Condition.create ();
250250- } in
263263+ let t =
264264+ {
265265+ transport;
266266+ permission_callback = Options.permission_callback options;
267267+ permission_log = None;
268268+ hook_callbacks;
269269+ session_id = None;
270270+ control_responses = Hashtbl.create 16;
271271+ control_mutex = Eio.Mutex.create ();
272272+ control_condition = Eio.Condition.create ();
273273+ }
274274+ in
251275252276 (* Register hooks and send initialize if hooks are configured *)
253253- (match Options.hooks options with
254254- | Some hooks_config ->
277277+ Options.hooks options
278278+ |> Option.iter (fun hooks_config ->
255279 Log.info (fun m -> m "Registering hooks...");
256280257281 (* Build hooks configuration with callback IDs as (string * Jsont.json) list *)
258258- let hooks_list = List.map (fun (event, matchers) ->
259259- let event_name = Hooks.event_to_string event in
260260- let matcher_wires = List.map (fun matcher ->
261261- let callback_ids = List.map (fun callback ->
262262- let callback_id = Printf.sprintf "hook_%d" !next_callback_id in
263263- incr next_callback_id;
264264- Hashtbl.add hook_callbacks callback_id callback;
265265- Log.debug (fun m -> m "Registered callback: %s for event: %s" callback_id event_name);
266266- callback_id
267267- ) matcher.Hooks.callbacks in
268268- Hook_matcher_wire.{ matcher = matcher.Hooks.matcher; hook_callback_ids = callback_ids }
269269- ) matchers in
270270- (event_name, Hook_matcher_wire.encode matcher_wires)
271271- ) hooks_config in
282282+ let hooks_list =
283283+ List.map
284284+ (fun (event, matchers) ->
285285+ let event_name = Hooks.event_to_string event in
286286+ let matcher_wires =
287287+ List.map
288288+ (fun matcher ->
289289+ let callback_ids =
290290+ List.map
291291+ (fun callback ->
292292+ let callback_id =
293293+ Printf.sprintf "hook_%d" !next_callback_id
294294+ in
295295+ incr next_callback_id;
296296+ Hashtbl.add hook_callbacks callback_id callback;
297297+ Log.debug (fun m ->
298298+ m "Registered callback: %s for event: %s"
299299+ callback_id event_name);
300300+ callback_id)
301301+ matcher.Hooks.callbacks
302302+ in
303303+ Hook_matcher_wire.
304304+ {
305305+ matcher = matcher.Hooks.matcher;
306306+ hook_callback_ids = callback_ids;
307307+ })
308308+ matchers
309309+ in
310310+ (event_name, Hook_matcher_wire.encode matcher_wires))
311311+ hooks_config
312312+ in
272313273314 (* Create initialize request using Sdk_control codec *)
274315 let request = Sdk_control.Request.initialize ~hooks:hooks_list () in
275275- let ctrl_req = Sdk_control.create_request ~request_id:"init_hooks" ~request () in
276276- let initialize_msg = match Jsont.Json.encode Sdk_control.jsont ctrl_req with
277277- | Ok json -> json
278278- | Error msg -> failwith ("Failed to encode initialize request: " ^ msg)
316316+ let ctrl_req =
317317+ Sdk_control.create_request ~request_id:"init_hooks" ~request ()
318318+ in
319319+ let initialize_msg =
320320+ Jsont.Json.encode Sdk_control.jsont ctrl_req
321321+ |> Err.get_ok ~msg:"Failed to encode initialize request: "
279322 in
280323 Log.info (fun m -> m "Sending hooks initialize request");
281281- Transport.send t.transport initialize_msg
282282- | None -> ());
324324+ Transport.send t.transport initialize_msg);
283325284326 t
285327···300342 let json = Message.User.to_json user_msg in
301343 Transport.send t.transport json
302344303303-let receive t =
304304- handle_messages t
345345+let receive t = handle_messages t
305346306347let receive_all t =
307348 let rec collect acc seq =
308349 match seq () with
309309- | Seq.Nil ->
310310- Log.debug (fun m -> m "End of message sequence (%d messages)" (List.length acc));
350350+ | Seq.Nil ->
351351+ Log.debug (fun m ->
352352+ m "End of message sequence (%d messages)" (List.length acc));
311353 List.rev acc
312312- | Seq.Cons (Message.Result _ as msg, _) ->
354354+ | Seq.Cons ((Message.Result _ as msg), _) ->
313355 Log.debug (fun m -> m "Received final Result message");
314356 List.rev (msg :: acc)
315315- | Seq.Cons (msg, rest) ->
316316- collect (msg :: acc) rest
357357+ | Seq.Cons (msg, rest) -> collect (msg :: acc) rest
317358 in
318359 collect [] (handle_messages t)
319360320320-let interrupt t =
321321- Transport.interrupt t.transport
361361+let interrupt t = Transport.interrupt t.transport
322362323363let discover_permissions t =
324364 let log = ref [] in
325365 let callback = Permissions.discovery_callback log in
326326- { t with
327327- permission_callback = Some callback;
328328- permission_log = Some log
329329- }
366366+ { t with permission_callback = Some callback; permission_log = Some log }
330367331368let get_discovered_permissions t =
332332- match t.permission_log with
333333- | Some log -> !log
334334- | None -> []
369369+ t.permission_log |> Option.map ( ! ) |> Option.value ~default:[]
335370336371let with_permission_callback t callback =
337372 { t with permission_callback = Some callback }
···340375let send_control_request t ~request_id request =
341376 (* Send the control request *)
342377 let control_msg = Sdk_control.create_request ~request_id ~request () in
343343- let json = match Jsont.Json.encode Sdk_control.jsont control_msg with
344344- | Ok j -> j
345345- | Error msg -> failwith ("Failed to encode control request: " ^ msg)
378378+ let json =
379379+ Jsont.Json.encode Sdk_control.jsont control_msg
380380+ |> Err.get_ok ~msg:"Failed to encode control request: "
346381 in
347382 Log.info (fun m -> m "Sending control request: %s" (json_to_string json));
348383 Transport.send t.transport json;
349384350385 (* Wait for the response with timeout *)
351351- let max_wait = 10.0 in (* 10 seconds timeout *)
386386+ let max_wait = 10.0 in
387387+ (* 10 seconds timeout *)
352388 let start_time = Unix.gettimeofday () in
353389354390 let rec wait_for_response () =
355391 Eio.Mutex.use_rw ~protect:false t.control_mutex (fun () ->
356356- match Hashtbl.find_opt t.control_responses request_id with
357357- | Some response_json ->
358358- (* Remove it from the table *)
359359- Hashtbl.remove t.control_responses request_id;
360360- response_json
361361- | None ->
362362- let elapsed = Unix.gettimeofday () -. start_time in
363363- if elapsed > max_wait then
364364- raise (Failure (Printf.sprintf "Timeout waiting for control response: %s" request_id))
365365- else (
366366- (* Release mutex and wait for signal *)
367367- Eio.Condition.await_no_mutex t.control_condition;
368368- wait_for_response ()
369369- )
370370- )
392392+ match Hashtbl.find_opt t.control_responses request_id with
393393+ | Some response_json ->
394394+ (* Remove it from the table *)
395395+ Hashtbl.remove t.control_responses request_id;
396396+ response_json
397397+ | None ->
398398+ let elapsed = Unix.gettimeofday () -. start_time in
399399+ if elapsed > max_wait then
400400+ raise
401401+ (Failure
402402+ (Printf.sprintf "Timeout waiting for control response: %s"
403403+ request_id))
404404+ else (
405405+ (* Release mutex and wait for signal *)
406406+ Eio.Condition.await_no_mutex t.control_condition;
407407+ wait_for_response ()))
371408 in
372409373410 let response_json = wait_for_response () in
374374- Log.debug (fun m -> m "Received control response: %s" (json_to_string response_json));
411411+ Log.debug (fun m ->
412412+ m "Received control response: %s" (json_to_string response_json));
375413376414 (* Parse the response - extract the "response" field using jsont codec *)
377377- let response_field_codec = Jsont.Object.map ~kind:"ResponseField" Fun.id
415415+ let response_field_codec =
416416+ Jsont.Object.map ~kind:"ResponseField" Fun.id
378417 |> Jsont.Object.mem "response" Jsont.json ~enc:Fun.id
379418 |> Jsont.Object.finish
380419 in
381381- let response_data = match Jsont.Json.decode response_field_codec response_json with
382382- | Ok r -> r
383383- | Error msg -> raise (Invalid_argument ("Failed to extract response field: " ^ msg))
420420+ let response_data =
421421+ Jsont.Json.decode response_field_codec response_json
422422+ |> Err.get_ok' ~msg:"Failed to extract response field: "
384423 in
385385- let response = match Jsont.Json.decode Sdk_control.Response.jsont response_data with
386386- | Ok r -> r
387387- | Error msg -> raise (Invalid_argument ("Failed to decode response: " ^ msg))
424424+ let response =
425425+ Jsont.Json.decode Sdk_control.Response.jsont response_data
426426+ |> Err.get_ok' ~msg:"Failed to decode response: "
388427 in
389428 match response with
390429 | Sdk_control.Response.Success s -> s.response
···395434 let request_id = Printf.sprintf "set_perm_mode_%f" (Unix.gettimeofday ()) in
396435 let request = Sdk_control.Request.set_permission_mode ~mode () in
397436 let _response = send_control_request t ~request_id request in
398398- Log.info (fun m -> m "Permission mode set to: %s" (Permissions.Mode.to_string mode))
437437+ Log.info (fun m ->
438438+ m "Permission mode set to: %s" (Permissions.Mode.to_string mode))
399439400440let set_model t model =
401441 let model_str = Model.to_string model in
···403443 let request = Sdk_control.Request.set_model ~model:model_str () in
404444 let _response = send_control_request t ~request_id request in
405445 Log.info (fun m -> m "Model set to: %s" model_str)
406406-407407-let set_model_string t model_str =
408408- set_model t (Model.of_string model_str)
409446410447let get_server_info t =
411448 let request_id = Printf.sprintf "get_server_info_%f" (Unix.gettimeofday ()) in
412449 let request = Sdk_control.Request.get_server_info () in
413413- match send_control_request t ~request_id request with
414414- | Some response_data ->
415415- let server_info = match Jsont.Json.decode Sdk_control.Server_info.jsont response_data with
416416- | Ok si -> si
417417- | Error msg -> raise (Invalid_argument ("Failed to decode server info: " ^ msg))
418418- in
419419- Log.info (fun m -> m "Retrieved server info: %a" (Jsont.pp_value Sdk_control.Server_info.jsont ()) server_info);
420420- server_info
421421- | None ->
422422- raise (Failure "No response data from get_server_info request")
450450+ let response_data =
451451+ send_control_request t ~request_id request
452452+ |> Option.to_result ~none:"No response data from get_server_info request"
453453+ |> Err.get_ok ~msg:""
454454+ in
455455+ let server_info =
456456+ Jsont.Json.decode Sdk_control.Server_info.jsont response_data
457457+ |> Err.get_ok' ~msg:"Failed to decode server info: "
458458+ in
459459+ Log.info (fun m ->
460460+ m "Retrieved server info: %a"
461461+ (Jsont.pp_value Sdk_control.Server_info.jsont ())
462462+ server_info);
463463+ server_info
+31-35
lib/client.mli
···8899 {[
1010 Eio.Switch.run @@ fun sw ->
1111- let client = Client.create ~sw ~process_mgr () in
1212- Client.query client "What is 2+2?";
1111+ let client = Client.create ~sw ~process_mgr () in
1212+ Client.query client "What is 2+2?";
13131414- let messages = Client.receive_all client in
1515- List.iter (function
1414+ let messages = Client.receive_all client in
1515+ List.iter
1616+ (function
1617 | Message.Assistant msg ->
1718 Printf.printf "Claude: %s\n" (Message.Assistant.text msg)
1818- | _ -> ()
1919- ) messages
1919+ | _ -> ())
2020+ messages
2021 ]}
21222223 {2 Features}
···29303031 {2 Message Flow}
31323232- 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
3333+ 1. Create a client with {!create} 2. Send messages with {!query} or
3434+ {!send_message} 3. Receive responses with {!receive} or {!receive_all} 4.
3535+ Continue multi-turn conversations by sending more messages 5. Client
3636+ automatically cleans up when the switch exits
37373838 {2 Advanced Features}
3939···4141 - Mid-conversation model switching and permission mode changes
4242 - Server capability introspection *)
43434444-(** The log source for client operations *)
4544val src : Logs.Src.t
4545+(** The log source for client operations *)
46464747type t
4848(** The type of Claude clients. *)
···5656 ?options:Options.t ->
5757 sw:Eio.Switch.t ->
5858 process_mgr:_ Eio.Process.mgr ->
5959- unit -> t
5959+ unit ->
6060+ t
6061(** [create ?options ~sw ~process_mgr ()] creates a new Claude client.
61626263 @param options Configuration options (defaults to {!Options.default})
···6667val query : t -> string -> unit
6768(** [query t prompt] sends a text message to Claude.
68696969- This is a convenience function for simple string messages. For more
7070- complex messages with tool results or multiple content blocks, use
7171- {!send_message} instead. *)
7070+ This is a convenience function for simple string messages. For more complex
7171+ messages with tool results or multiple content blocks, use {!send_message}
7272+ instead. *)
72737374val send_message : t -> Message.t -> unit
7475(** [send_message t msg] sends a message to Claude.
···9293val receive_all : t -> Message.t list
9394(** [receive_all t] collects all messages into a list.
94959595- This is a convenience function that consumes the {!receive} sequence.
9696- Use this when you want to process all messages at once rather than
9797- streaming them. *)
9696+ This is a convenience function that consumes the {!receive} sequence. Use
9797+ this when you want to process all messages at once rather than streaming
9898+ them. *)
989999100val interrupt : t -> unit
100101(** [interrupt t] sends an interrupt signal to stop Claude's execution. *)
···103104(** [discover_permissions t] enables permission discovery mode.
104105105106 In discovery mode, all tool usage is logged but allowed. Use
106106- {!get_discovered_permissions} to retrieve the list of permissions
107107- that were requested during execution.
107107+ {!get_discovered_permissions} to retrieve the list of permissions that were
108108+ requested during execution.
108109109110 This is useful for understanding what permissions your prompt requires. *)
110111111112val get_discovered_permissions : t -> Permissions.Rule.t list
112112-(** [get_discovered_permissions t] returns permissions discovered during execution.
113113+(** [get_discovered_permissions t] returns permissions discovered during
114114+ execution.
113115114116 Only useful after enabling {!discover_permissions}. *)
115117116118val with_permission_callback : t -> Permissions.callback -> t
117119(** [with_permission_callback t callback] updates the permission callback.
118120119119- Allows dynamically changing the permission callback without recreating
120120- the client. *)
121121+ Allows dynamically changing the permission callback without recreating the
122122+ client. *)
121123122124(** {1 Dynamic Control Methods}
123125124124- These methods allow you to change Claude's behavior mid-conversation
125125- without recreating the client. This is useful for:
126126+ These methods allow you to change Claude's behavior mid-conversation without
127127+ recreating the client. This is useful for:
126128127129 - Adjusting permission strictness based on user feedback
128130 - Switching to faster/cheaper models for simple tasks
···173175 Printf.printf "Claude CLI version: %s\n"
174176 (Sdk_control.Server_info.version info);
175177 Printf.printf "Capabilities: %s\n"
176176- (String.concat ", " (Sdk_control.Server_info.capabilities info));
178178+ (String.concat ", " (Sdk_control.Server_info.capabilities info))
177179 ]} *)
178180179181val set_permission_mode : t -> Permissions.Mode.t -> unit
180182(** [set_permission_mode t mode] changes the permission mode mid-conversation.
181183182182- This allows switching between permission modes without recreating the client:
184184+ This allows switching between permission modes without recreating the
185185+ client:
183186 - {!Permissions.Mode.Default} - Prompt for all permissions
184187 - {!Permissions.Mode.Accept_edits} - Auto-accept file edits
185188 - {!Permissions.Mode.Plan} - Planning mode with restricted execution
···194197 - [`Sonnet_4_5] - Most capable, balanced performance
195198 - [`Opus_4] - Maximum capability for complex tasks
196199 - [`Haiku_4] - Fast and cost-effective
197197-198198- @raise Failure if the model is invalid or unavailable *)
199199-200200-val set_model_string : t -> string -> unit
201201-(** [set_model_string t model] switches to a different AI model using a string.
202202-203203- This is a convenience function that parses the string using {!Model.of_string}.
204200205201 @raise Failure if the model is invalid or unavailable *)
206202
+42-40
lib/content_block.ml
···11let src = Logs.Src.create "claude.content_block" ~doc:"Claude content blocks"
22-module Log = (val Logs.src_log src : Logs.LOG)
3233+module Log = (val Logs.src_log src : Logs.LOG)
4455module Text = struct
66- type t = {
77- text : string;
88- unknown : Unknown.t;
99- }
66+ type t = { text : string; unknown : Unknown.t }
107118 let create text = { text; unknown = Unknown.empty }
1212-139 let make text unknown = { text; unknown }
1410 let text t = t.text
1511 let unknown t = t.unknown
···2925 let jsont = Jsont.json
30263127 let of_string_pairs pairs =
3232- Jsont.Json.object' (List.map (fun (k, v) ->
3333- Jsont.Json.mem (Jsont.Json.name k) (Jsont.Json.string v)
3434- ) pairs)
2828+ Jsont.Json.object'
2929+ (List.map
3030+ (fun (k, v) ->
3131+ Jsont.Json.mem (Jsont.Json.name k) (Jsont.Json.string v))
3232+ pairs)
35333634 let of_assoc (assoc : (string * Jsont.json) list) : t =
3737- Jsont.Json.object' (List.map (fun (k, v) -> Jsont.Json.mem (Jsont.Json.name k) v) assoc)
3535+ Jsont.Json.object'
3636+ (List.map (fun (k, v) -> Jsont.Json.mem (Jsont.Json.name k) v) assoc)
38373938 (* Helper to decode an optional field with a given codec *)
4039 let get_opt (type a) (codec : a Jsont.t) t key : a option =
4141- let field_codec = Jsont.Object.map ~kind:"field" (fun v -> v)
4040+ let field_codec =
4141+ Jsont.Object.map ~kind:"field" (fun v -> v)
4242 |> Jsont.Object.opt_mem key codec ~enc:Fun.id
4343 |> Jsont.Object.finish
4444 in
4545- match Jsont.Json.decode field_codec t with
4646- | Ok v -> v
4747- | Error _ -> None
4545+ match Jsont.Json.decode field_codec t with Ok v -> v | Error _ -> None
48464947 let get_string t key = get_opt Jsont.string t key
5048 let get_int t key = get_opt Jsont.int t key
···5452 let keys t =
5553 (* Decode as object with all members captured as unknown *)
5654 match t with
5757- | Jsont.Object (members, _) -> List.map (fun ((name, _), _) -> name) members
5555+ | Jsont.Object (members, _) ->
5656+ List.map (fun ((name, _), _) -> name) members
5857 | _ -> []
5959-6058 end
61596262- type t = {
6363- id : string;
6464- name : string;
6565- input : Input.t;
6666- unknown : Unknown.t;
6767- }
6060+ type t = { id : string; name : string; input : Input.t; unknown : Unknown.t }
68616962 let create ~id ~name ~input = { id; name; input; unknown = Unknown.empty }
7070-7163 let make id name input unknown = { id; name; input; unknown }
7264 let id t = t.id
7365 let name t = t.name
···96889789 let make tool_use_id content is_error unknown =
9890 { tool_use_id; content; is_error; unknown }
9191+9992 let tool_use_id t = t.tool_use_id
10093 let content t = t.content
10194 let is_error t = t.is_error
···111104end
112105113106module Thinking = struct
114114- type t = {
115115- thinking : string;
116116- signature : string;
117117- unknown : Unknown.t;
118118- }
107107+ type t = { thinking : string; signature : string; unknown : Unknown.t }
119108120120- let create ~thinking ~signature = { thinking; signature; unknown = Unknown.empty }
109109+ let create ~thinking ~signature =
110110+ { thinking; signature; unknown = Unknown.empty }
121111122112 let make thinking signature unknown = { thinking; signature; unknown }
123113 let thinking t = t.thinking
···140130141131let text s = Text (Text.create s)
142132let tool_use ~id ~name ~input = Tool_use (Tool_use.create ~id ~name ~input)
133133+143134let tool_result ~tool_use_id ?content ?is_error () =
144135 Tool_result (Tool_result.create ~tool_use_id ?content ?is_error ())
136136+145137let thinking ~thinking ~signature =
146138 Thinking (Thinking.create ~thinking ~signature)
147139···149141 let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
150142151143 let case_text = case_map "text" Text.jsont (fun v -> Text v) in
152152- let case_tool_use = case_map "tool_use" Tool_use.jsont (fun v -> Tool_use v) in
153153- let case_tool_result = case_map "tool_result" Tool_result.jsont (fun v -> Tool_result v) in
154154- let case_thinking = case_map "thinking" Thinking.jsont (fun v -> Thinking v) in
144144+ let case_tool_use =
145145+ case_map "tool_use" Tool_use.jsont (fun v -> Tool_use v)
146146+ in
147147+ let case_tool_result =
148148+ case_map "tool_result" Tool_result.jsont (fun v -> Tool_result v)
149149+ in
150150+ let case_thinking =
151151+ case_map "thinking" Thinking.jsont (fun v -> Thinking v)
152152+ in
155153156154 let enc_case = function
157155 | Text v -> Jsont.Object.Case.value case_text v
···160158 | Thinking v -> Jsont.Object.Case.value case_thinking v
161159 in
162160163163- let cases = Jsont.Object.Case.[
164164- make case_text;
165165- make case_tool_use;
166166- make case_tool_result;
167167- make case_thinking
168168- ] in
161161+ let cases =
162162+ Jsont.Object.Case.
163163+ [
164164+ make case_text;
165165+ make case_tool_use;
166166+ make case_tool_result;
167167+ make case_thinking;
168168+ ]
169169+ in
169170170171 Jsont.Object.map ~kind:"Content_block" Fun.id
171172 |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
172172- ~tag_to_string:Fun.id ~tag_compare:String.compare
173173+ ~tag_to_string:Fun.id ~tag_compare:String.compare
173174 |> Jsont.Object.finish
174175175176let log_received t =
176176- Log.debug (fun m -> m "Received content block: %a" (Jsont.pp_value jsont ()) t)
177177+ Log.debug (fun m ->
178178+ m "Received content block: %a" (Jsont.pp_value jsont ()) t)
177179178180let log_sending t =
179181 Log.debug (fun m -> m "Sending content block: %a" (Jsont.pp_value jsont ()) t)
+28-22
lib/content_block.mli
···11(** Content blocks for Claude messages.
2233- This module defines the various types of content blocks that can appear
44- in Claude messages, including text, tool use, tool results, and thinking blocks. *)
33+ This module defines the various types of content blocks that can appear in
44+ Claude messages, including text, tool use, tool results, and thinking
55+ blocks. *)
5677+val src : Logs.Src.t
68(** The log source for content block operations *)
77-val src : Logs.Src.t
89910(** {1 Text Blocks} *)
1011···2425 (** [unknown t] returns any unknown fields from JSON parsing. *)
25262627 val jsont : t Jsont.t
2727- (** [jsont] is the Jsont codec for text blocks.
2828- Use [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
2929- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
2828+ (** [jsont] is the Jsont codec for text blocks. Use [Jsont.Json.encode jsont]
2929+ and [Jsont.Json.decode jsont] for serialization. Use
3030+ [Jsont.pp_value jsont ()] for pretty-printing. *)
3031end
31323233(** {1 Tool Use Blocks} *)
···4445 (** [jsont] is the Jsont codec for tool inputs. *)
45464647 val of_string_pairs : (string * string) list -> t
4747- (** [of_string_pairs pairs] creates tool input from string key-value pairs. *)
4848+ (** [of_string_pairs pairs] creates tool input from string key-value pairs.
4949+ *)
48504951 val of_assoc : (string * Jsont.json) list -> t
5052 (** [of_assoc assoc] creates tool input from an association list. *)
···63656466 val keys : t -> string list
6567 (** [keys t] returns all keys in the input. *)
6666-6768 end
68696970 type t
···8889 (** [unknown t] returns any unknown fields from JSON parsing. *)
89909091 val jsont : t Jsont.t
9191- (** [jsont] is the Jsont codec for tool use blocks.
9292- Use [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
9292+ (** [jsont] is the Jsont codec for tool use blocks. Use
9393+ [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
9394 Use [Jsont.pp_value jsont ()] for pretty-printing. *)
9495end
9596···101102 type t
102103 (** The type of tool result blocks. *)
103104104104- val create : tool_use_id:string -> ?content:string -> ?is_error:bool -> unit -> t
105105- (** [create ~tool_use_id ?content ?is_error ()] creates a new tool result block.
105105+ val create :
106106+ tool_use_id:string -> ?content:string -> ?is_error:bool -> unit -> t
107107+ (** [create ~tool_use_id ?content ?is_error ()] creates a new tool result
108108+ block.
106109 @param tool_use_id The ID of the corresponding tool use block
107110 @param content Optional result content
108111 @param is_error Whether the tool execution resulted in an error *)
···120123 (** [unknown t] returns any unknown fields from JSON parsing. *)
121124122125 val jsont : t Jsont.t
123123- (** [jsont] is the Jsont codec for tool result blocks.
124124- Use [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
126126+ (** [jsont] is the Jsont codec for tool result blocks. Use
127127+ [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
125128 Use [Jsont.pp_value jsont ()] for pretty-printing. *)
126129end
127130···148151 (** [unknown t] returns any unknown fields from JSON parsing. *)
149152150153 val jsont : t Jsont.t
151151- (** [jsont] is the Jsont codec for thinking blocks.
152152- Use [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
154154+ (** [jsont] is the Jsont codec for thinking blocks. Use
155155+ [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
153156 Use [Jsont.pp_value jsont ()] for pretty-printing. *)
154157end
155158···160163 | Tool_use of Tool_use.t
161164 | Tool_result of Tool_result.t
162165 | Thinking of Thinking.t
163163-(** The type of content blocks, which can be text, tool use, tool result, or thinking. *)
166166+ (** The type of content blocks, which can be text, tool use, tool result,
167167+ or thinking. *)
164168165169val text : string -> t
166170(** [text s] creates a text content block. *)
···168172val tool_use : id:string -> name:string -> input:Tool_use.Input.t -> t
169173(** [tool_use ~id ~name ~input] creates a tool use content block. *)
170174171171-val tool_result : tool_use_id:string -> ?content:string -> ?is_error:bool -> unit -> t
172172-(** [tool_result ~tool_use_id ?content ?is_error ()] creates a tool result content block. *)
175175+val tool_result :
176176+ tool_use_id:string -> ?content:string -> ?is_error:bool -> unit -> t
177177+(** [tool_result ~tool_use_id ?content ?is_error ()] creates a tool result
178178+ content block. *)
173179174180val thinking : thinking:string -> signature:string -> t
175181(** [thinking ~thinking ~signature] creates a thinking content block. *)
176182177183val jsont : t Jsont.t
178178-(** [jsont] is the Jsont codec for content blocks.
179179- Use [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
180180- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
184184+(** [jsont] is the Jsont codec for content blocks. Use [Jsont.Json.encode jsont]
185185+ and [Jsont.Json.decode jsont] for serialization. Use
186186+ [Jsont.pp_value jsont ()] for pretty-printing. *)
181187182188(** {1 Logging} *)
183189
+16-16
lib/control.ml
···11let src = Logs.Src.create "claude.control" ~doc:"Claude control messages"
22+23module Log = (val Logs.src_log src : Logs.LOG)
3445type t = {
···910}
10111112let jsont =
1212- Jsont.Object.map ~kind:"Control"
1313- (fun request_id subtype data unknown -> {request_id; subtype; data; unknown})
1313+ Jsont.Object.map ~kind:"Control" (fun request_id subtype data unknown ->
1414+ { request_id; subtype; data; unknown })
1415 |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun t -> t.request_id)
1516 |> Jsont.Object.mem "subtype" Jsont.string ~enc:(fun t -> t.subtype)
1617 |> Jsont.Object.mem "data" Jsont.json ~enc:(fun t -> t.data)
···2526let data t = t.data
26272728let to_json t =
2828- match Jsont_bytesrw.encode_string ~format:Jsont.Minify jsont t with
2929- | Ok s ->
3030- (match Jsont_bytesrw.decode_string' Jsont.json s with
3131- | Ok json -> json
3232- | Error e -> failwith (Jsont.Error.to_string e))
3333- | Error e -> failwith e
2929+ Jsont_bytesrw.encode_string ~format:Jsont.Minify jsont t
3030+ |> Err.get_ok ~msg:"Control.to_json: "
3131+ |> Jsont_bytesrw.decode_string' Jsont.json
3232+ |> Result.map_error Jsont.Error.to_string
3333+ |> Err.get_ok ~msg:"Control.to_json: "
34343535let of_json json =
3636- match Jsont_bytesrw.encode_string ~format:Jsont.Minify Jsont.json json with
3737- | Ok s ->
3838- (match Jsont_bytesrw.decode_string jsont s with
3939- | Ok t -> t
4040- | Error e -> raise (Invalid_argument ("Control.of_json: " ^ e)))
4141- | Error e -> raise (Invalid_argument ("Control.of_json: " ^ e))
3636+ Jsont_bytesrw.encode_string ~format:Jsont.Minify Jsont.json json
3737+ |> Err.get_ok' ~msg:"Control.of_json: "
3838+ |> Jsont_bytesrw.decode_string jsont
3939+ |> Err.get_ok' ~msg:"Control.of_json: "
42404341let log_received t =
4444- Log.debug (fun m -> m "Received control message: %a" (Jsont.pp_value jsont ()) t)
4242+ Log.debug (fun m ->
4343+ m "Received control message: %a" (Jsont.pp_value jsont ()) t)
45444645let log_sending t =
4747- Log.debug (fun m -> m "Sending control message: %a" (Jsont.pp_value jsont ()) t)4646+ Log.debug (fun m ->
4747+ m "Sending control message: %a" (Jsont.pp_value jsont ()) t)
+2-2
lib/control.mli
···44 including session control, cancellation requests, and other operational
55 commands. *)
6677-(** The log source for control message operations *)
87val src : Logs.Src.t
88+(** The log source for control message operations *)
991010type t
1111(** The type of control messages. *)
···4141(** [log_received t] logs that a control message was received. *)
42424343val log_sending : t -> unit
4444-(** [log_sending t] logs that a control message is being sent. *)4444+(** [log_sending t] logs that a control message is being sent. *)
···49495050 let options = Claude.Options.create ~hooks:(Some hooks) () in
5151 let client = Claude.Client.create ~options ~sw ~process_mgr () in
5252- ]}
5353-*)
5252+ ]} *)
54535454+val src : Logs.Src.t
5555(** The log source for hooks *)
5656-val src : Logs.Src.t
57565857(** {1 Hook Events} *)
59586059(** Hook event types *)
6160type event =
6262- | Pre_tool_use (** Fires before a tool is executed *)
6363- | Post_tool_use (** Fires after a tool completes *)
6464- | User_prompt_submit (** Fires when user submits a prompt *)
6565- | Stop (** Fires when conversation stops *)
6666- | Subagent_stop (** Fires when a subagent stops *)
6767- | Pre_compact (** Fires before message compaction *)
6161+ | Pre_tool_use (** Fires before a tool is executed *)
6262+ | Post_tool_use (** Fires after a tool completes *)
6363+ | User_prompt_submit (** Fires when user submits a prompt *)
6464+ | Stop (** Fires when conversation stops *)
6565+ | Subagent_stop (** Fires when a subagent stops *)
6666+ | Pre_compact (** Fires before message compaction *)
68676968val event_to_string : event -> string
7069val event_of_string : string -> event
···7372(** {1 Context} *)
74737574module Context : sig
7676- type t = {
7777- signal: unit option;
7878- unknown : Unknown.t;
7979- }
7575+ type t = { signal : unit option; unknown : Unknown.t }
80768177 val create : ?signal:unit option -> ?unknown:Unknown.t -> unit -> t
8278 val signal : t -> unit option
···88848985type decision =
9086 | Continue (** Allow the action to proceed *)
9191- | Block (** Block the action *)
8787+ | Block (** Block the action *)
92889389val decision_jsont : decision Jsont.t
94909591(** {1 Generic Hook Result} *)
96929797-(** Generic result structure for hooks *)
9893type result = {
9999- decision: decision option;
100100- system_message: string option;
101101- hook_specific_output: Jsont.json option;
102102- unknown: Unknown.t;
9494+ decision : decision option;
9595+ system_message : string option;
9696+ hook_specific_output : Jsont.json option;
9797+ unknown : Unknown.t;
10398}
9999+(** Generic result structure for hooks *)
104100105101val result_jsont : result Jsont.t
106102···108104109105(** PreToolUse hook - fires before tool execution *)
110106module PreToolUse : sig
111111- (** Typed input for PreToolUse hooks *)
112107 type input = {
113113- session_id: string;
114114- transcript_path: string;
115115- tool_name: string;
116116- tool_input: Jsont.json;
117117- unknown: Unknown.t;
108108+ session_id : string;
109109+ transcript_path : string;
110110+ tool_name : string;
111111+ tool_input : Jsont.json;
112112+ unknown : Unknown.t;
118113 }
114114+ (** Typed input for PreToolUse hooks *)
119115120116 type t = input
121117122122- (** Parse hook input from JSON *)
123118 val of_json : Jsont.json -> t
119119+ (** Parse hook input from JSON *)
124120121121+ val session_id : t -> string
125122 (** {2 Accessors} *)
126126- val session_id : t -> string
123123+127124 val transcript_path : t -> string
128125 val tool_name : t -> string
129126 val tool_input : t -> Jsont.json
130127 val unknown : t -> Unknown.t
131131-132128 val input_jsont : input Jsont.t
133129134134- (** Permission decision for tool usage *)
135130 type permission_decision = [ `Allow | `Deny | `Ask ]
131131+ (** Permission decision for tool usage *)
136132137133 val permission_decision_jsont : permission_decision Jsont.t
138134139139- (** Typed output for PreToolUse hooks *)
140135 type output = {
141141- permission_decision: permission_decision option;
142142- permission_decision_reason: string option;
143143- updated_input: Jsont.json option;
144144- unknown: Unknown.t;
136136+ permission_decision : permission_decision option;
137137+ permission_decision_reason : string option;
138138+ updated_input : Jsont.json option;
139139+ unknown : Unknown.t;
145140 }
141141+ (** Typed output for PreToolUse hooks *)
146142147143 val output_jsont : output Jsont.t
148144145145+ val allow :
146146+ ?reason:string ->
147147+ ?updated_input:Jsont.json ->
148148+ ?unknown:Unknown.t ->
149149+ unit ->
150150+ output
149151 (** {2 Response Builders} *)
150150- val allow : ?reason:string -> ?updated_input:Jsont.json -> ?unknown:Unknown.t -> unit -> output
152152+151153 val deny : ?reason:string -> ?unknown:Unknown.t -> unit -> output
152154 val ask : ?reason:string -> ?unknown:Unknown.t -> unit -> output
153155 val continue : ?unknown:Unknown.t -> unit -> output
154156155155- (** Convert output to JSON for hook_specific_output *)
156157 val output_to_json : output -> Jsont.json
158158+ (** Convert output to JSON for hook_specific_output *)
157159end
158160159161(** PostToolUse hook - fires after tool execution *)
160162module PostToolUse : sig
161163 type input = {
162162- session_id: string;
163163- transcript_path: string;
164164- tool_name: string;
165165- tool_input: Jsont.json;
166166- tool_response: Jsont.json;
167167- unknown: Unknown.t;
164164+ session_id : string;
165165+ transcript_path : string;
166166+ tool_name : string;
167167+ tool_input : Jsont.json;
168168+ tool_response : Jsont.json;
169169+ unknown : Unknown.t;
168170 }
169171170172 type t = input
171173172174 val of_json : Jsont.json -> t
173173-174175 val session_id : t -> string
175176 val transcript_path : t -> string
176177 val tool_name : t -> string
177178 val tool_input : t -> Jsont.json
178179 val tool_response : t -> Jsont.json
179180 val unknown : t -> Unknown.t
180180-181181 val input_jsont : input Jsont.t
182182183183 type output = {
184184- decision: decision option;
185185- reason: string option;
186186- additional_context: string option;
187187- unknown: Unknown.t;
184184+ decision : decision option;
185185+ reason : string option;
186186+ additional_context : string option;
187187+ unknown : Unknown.t;
188188 }
189189190190 val output_jsont : output Jsont.t
191191192192- val continue : ?additional_context:string -> ?unknown:Unknown.t -> unit -> output
193193- val block : ?reason:string -> ?additional_context:string -> ?unknown:Unknown.t -> unit -> output
192192+ val continue :
193193+ ?additional_context:string -> ?unknown:Unknown.t -> unit -> output
194194+195195+ val block :
196196+ ?reason:string ->
197197+ ?additional_context:string ->
198198+ ?unknown:Unknown.t ->
199199+ unit ->
200200+ output
201201+194202 val output_to_json : output -> Jsont.json
195203end
196204197205(** UserPromptSubmit hook - fires when user submits a prompt *)
198206module UserPromptSubmit : sig
199207 type input = {
200200- session_id: string;
201201- transcript_path: string;
202202- prompt: string;
203203- unknown: Unknown.t;
208208+ session_id : string;
209209+ transcript_path : string;
210210+ prompt : string;
211211+ unknown : Unknown.t;
204212 }
205213206214 type t = input
207215208216 val of_json : Jsont.json -> t
209209-210217 val session_id : t -> string
211218 val transcript_path : t -> string
212219 val prompt : t -> string
213220 val unknown : t -> Unknown.t
214214-215221 val input_jsont : input Jsont.t
216222217223 type output = {
218218- decision: decision option;
219219- reason: string option;
220220- additional_context: string option;
221221- unknown: Unknown.t;
224224+ decision : decision option;
225225+ reason : string option;
226226+ additional_context : string option;
227227+ unknown : Unknown.t;
222228 }
223229224230 val output_jsont : output Jsont.t
225231226226- val continue : ?additional_context:string -> ?unknown:Unknown.t -> unit -> output
232232+ val continue :
233233+ ?additional_context:string -> ?unknown:Unknown.t -> unit -> output
234234+227235 val block : ?reason:string -> ?unknown:Unknown.t -> unit -> output
228236 val output_to_json : output -> Jsont.json
229237end
···231239(** Stop hook - fires when conversation stops *)
232240module Stop : sig
233241 type input = {
234234- session_id: string;
235235- transcript_path: string;
236236- stop_hook_active: bool;
237237- unknown: Unknown.t;
242242+ session_id : string;
243243+ transcript_path : string;
244244+ stop_hook_active : bool;
245245+ unknown : Unknown.t;
238246 }
239247240248 type t = input
241249242250 val of_json : Jsont.json -> t
243243-244251 val session_id : t -> string
245252 val transcript_path : t -> string
246253 val stop_hook_active : t -> bool
247254 val unknown : t -> Unknown.t
248248-249255 val input_jsont : input Jsont.t
250256251257 type output = {
252252- decision: decision option;
253253- reason: string option;
254254- unknown: Unknown.t;
258258+ decision : decision option;
259259+ reason : string option;
260260+ unknown : Unknown.t;
255261 }
256262257263 val output_jsont : output Jsont.t
258258-259264 val continue : ?unknown:Unknown.t -> unit -> output
260265 val block : ?reason:string -> ?unknown:Unknown.t -> unit -> output
261266 val output_to_json : output -> Jsont.json
···264269(** SubagentStop hook - fires when a subagent stops *)
265270module SubagentStop : sig
266271 include module type of Stop
272272+267273 val of_json : Jsont.json -> t
268274end
269275270276(** PreCompact hook - fires before message compaction *)
271277module PreCompact : sig
272278 type input = {
273273- session_id: string;
274274- transcript_path: string;
275275- unknown: Unknown.t;
279279+ session_id : string;
280280+ transcript_path : string;
281281+ unknown : Unknown.t;
276282 }
277283278284 type t = input
279279-280285 type output = unit
281286282287 val of_json : Jsont.json -> t
283283-284288 val session_id : t -> string
285289 val transcript_path : t -> string
286290 val unknown : t -> Unknown.t
287287-288291 val input_jsont : input Jsont.t
289289-290292 val continue : unit -> output
291293 val output_to_json : output -> Jsont.json
292294end
293295294296(** {1 Callbacks} *)
295297298298+type callback =
299299+ input:Jsont.json -> tool_use_id:string option -> context:Context.t -> result
296300(** Generic callback function type.
297301298302 Callbacks receive:
···300304 - [tool_use_id]: Optional tool use ID
301305 - [context]: Hook context
302306303303- And return a generic [result] with optional hook-specific output.
304304-*)
305305-type callback =
306306- input:Jsont.json ->
307307- tool_use_id:string option ->
308308- context:Context.t ->
309309- result
307307+ And return a generic [result] with optional hook-specific output. *)
310308311309(** {1 Matchers} *)
312310313313-(** A matcher configuration *)
314311type matcher = {
315315- matcher: string option; (** Pattern to match (e.g., "Bash" or "Write|Edit") *)
316316- callbacks: callback list; (** Callbacks to invoke on match *)
312312+ matcher : string option;
313313+ (** Pattern to match (e.g., "Bash" or "Write|Edit") *)
314314+ callbacks : callback list; (** Callbacks to invoke on match *)
317315}
316316+(** A matcher configuration *)
318317319319-(** Hook configuration: map from events to matchers *)
320318type config = (event * matcher list) list
319319+(** Hook configuration: map from events to matchers *)
321320322321(** {1 Generic Result Builders} *)
323322324324-(** [continue ?system_message ?hook_specific_output ?unknown ()] creates a continue result *)
325325-val continue : ?system_message:string -> ?hook_specific_output:Jsont.json -> ?unknown:Unknown.t -> unit -> result
323323+val continue :
324324+ ?system_message:string ->
325325+ ?hook_specific_output:Jsont.json ->
326326+ ?unknown:Unknown.t ->
327327+ unit ->
328328+ result
329329+(** [continue ?system_message ?hook_specific_output ?unknown ()] creates a
330330+ continue result *)
326331327327-(** [block ?system_message ?hook_specific_output ?unknown ()] creates a block result *)
328328-val block : ?system_message:string -> ?hook_specific_output:Jsont.json -> ?unknown:Unknown.t -> unit -> result
332332+val block :
333333+ ?system_message:string ->
334334+ ?hook_specific_output:Jsont.json ->
335335+ ?unknown:Unknown.t ->
336336+ unit ->
337337+ result
338338+(** [block ?system_message ?hook_specific_output ?unknown ()] creates a block
339339+ result *)
329340330341(** {1 Configuration Builders} *)
331342332332-(** [matcher ?pattern callbacks] creates a matcher *)
333343val matcher : ?pattern:string -> callback list -> matcher
344344+(** [matcher ?pattern callbacks] creates a matcher *)
334345335335-(** Empty hooks configuration *)
336346val empty : config
347347+(** Empty hooks configuration *)
337348349349+val add : event -> matcher list -> config -> config
338350(** [add event matchers config] adds matchers for an event *)
339339-val add : event -> matcher list -> config -> config
340351341352(** {1 JSON Serialization} *)
342353
+65-47
lib/incoming.ml
···11-let src = Logs.Src.create "claude.incoming" ~doc:"Incoming messages from Claude CLI"
11+let src =
22+ Logs.Src.create "claude.incoming" ~doc:"Incoming messages from Claude CLI"
33+24module Log = (val Logs.src_log src : Logs.LOG)
3546(** Control request types for incoming control_request messages *)
···17191820 let jsont : t Jsont.t =
1921 let make tool_name input permission_suggestions =
2020- { tool_name; input; permission_suggestions = Option.value permission_suggestions ~default:[] }
2222+ {
2323+ tool_name;
2424+ input;
2525+ permission_suggestions =
2626+ Option.value permission_suggestions ~default:[];
2727+ }
2128 in
2229 Jsont.Object.map ~kind:"CanUseTool" make
2330 |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
2431 |> Jsont.Object.mem "input" Jsont.json ~enc:input
2532 |> Jsont.Object.opt_mem "permission_suggestions" (Jsont.list Jsont.json)
2626- ~enc:(fun t -> if t.permission_suggestions = [] then None else Some t.permission_suggestions)
3333+ ~enc:(fun t ->
3434+ if t.permission_suggestions = [] then None
3535+ else Some t.permission_suggestions)
2736 |> Jsont.Object.finish
2837 end
2938···4049 let tool_use_id t = t.tool_use_id
41504251 let jsont : t Jsont.t =
4343- let make callback_id input tool_use_id = { callback_id; input; tool_use_id } in
5252+ let make callback_id input tool_use_id =
5353+ { callback_id; input; tool_use_id }
5454+ in
4455 Jsont.Object.map ~kind:"HookCallback" make
4556 |> Jsont.Object.mem "callback_id" Jsont.string ~enc:callback_id
4657 |> Jsont.Object.mem "input" Jsont.json ~enc:input
···5566 | Unknown of string * Jsont.json
56675768 let request_of_json json =
5858- let subtype_codec = Jsont.Object.map ~kind:"Subtype" Fun.id
6969+ let subtype_codec =
7070+ Jsont.Object.map ~kind:"Subtype" Fun.id
5971 |> Jsont.Object.mem "subtype" Jsont.string ~enc:Fun.id
6072 |> Jsont.Object.finish
6173 in
6274 match Jsont.Json.decode subtype_codec json with
6375 | Error _ -> Unknown ("unknown", json)
6464- | Ok subtype ->
7676+ | Ok subtype -> (
6577 match subtype with
6666- | "can_use_tool" ->
6767- (match Jsont.Json.decode Can_use_tool.jsont json with
7878+ | "can_use_tool" -> (
7979+ match Jsont.Json.decode Can_use_tool.jsont json with
6880 | Ok r -> Can_use_tool r
6981 | Error _ -> Unknown (subtype, json))
7070- | "hook_callback" ->
7171- (match Jsont.Json.decode Hook_callback.jsont json with
8282+ | "hook_callback" -> (
8383+ match Jsont.Json.decode Hook_callback.jsont json with
7284 | Ok r -> Hook_callback r
7385 | Error _ -> Unknown (subtype, json))
7474- | _ -> Unknown (subtype, json)
8686+ | _ -> Unknown (subtype, json))
75878888+ type t = { request_id : string; request : request }
7689 (** Full control request message *)
7777- type t = {
7878- request_id : string;
7979- request : request;
8080- }
81908291 let request_id t = t.request_id
8392 let request t = t.request
···91100 let jsont : t Jsont.t =
92101 let dec json =
93102 let envelope_codec =
9494- Jsont.Object.map ~kind:"ControlRequestEnvelope" (fun request_id request_json -> (request_id, request_json))
103103+ Jsont.Object.map ~kind:"ControlRequestEnvelope"
104104+ (fun request_id request_json -> (request_id, request_json))
95105 |> Jsont.Object.mem "request_id" Jsont.string ~enc:fst
96106 |> Jsont.Object.mem "request" Jsont.json ~enc:snd
97107 |> Jsont.Object.finish
98108 in
99109 match Jsont.Json.decode envelope_codec json with
100100- | Error err -> failwith ("Failed to decode control_request envelope: " ^ err)
110110+ | Error err ->
111111+ failwith ("Failed to decode control_request envelope: " ^ err)
101112 | Ok (request_id, request_json) ->
102113 { request_id; request = request_of_json request_json }
103114 in
104115 let enc t =
105105- let request_json = match t.request with
106106- | Can_use_tool r ->
107107- (match Jsont.Json.encode Can_use_tool.jsont r with
116116+ let request_json =
117117+ match t.request with
118118+ | Can_use_tool r -> (
119119+ match Jsont.Json.encode Can_use_tool.jsont r with
108120 | Ok j -> j
109121 | Error err -> failwith ("Failed to encode Can_use_tool: " ^ err))
110110- | Hook_callback r ->
111111- (match Jsont.Json.encode Hook_callback.jsont r with
122122+ | Hook_callback r -> (
123123+ match Jsont.Json.encode Hook_callback.jsont r with
112124 | Ok j -> j
113125 | Error err -> failwith ("Failed to encode Hook_callback: " ^ err))
114126 | Unknown (_, j) -> j
115127 in
116116- Jsont.Json.object' [
117117- Jsont.Json.mem (Jsont.Json.name "type") (Jsont.Json.string "control_request");
118118- Jsont.Json.mem (Jsont.Json.name "request_id") (Jsont.Json.string t.request_id);
119119- Jsont.Json.mem (Jsont.Json.name "request") request_json;
120120- ]
128128+ Jsont.Json.object'
129129+ [
130130+ Jsont.Json.mem (Jsont.Json.name "type")
131131+ (Jsont.Json.string "control_request");
132132+ Jsont.Json.mem
133133+ (Jsont.Json.name "request_id")
134134+ (Jsont.Json.string t.request_id);
135135+ Jsont.Json.mem (Jsont.Json.name "request") request_json;
136136+ ]
121137 in
122138 Jsont.map ~kind:"ControlRequest" ~dec ~enc Jsont.json
123139end
···134150 "system", "result"), while control_response and control_request have single type values.
135151 Jsont's case_mem discriminator doesn't support multiple tags per case, so we implement
136152 a custom decoder/encoder. *)
137137-138138- let type_field_codec = Jsont.Object.map ~kind:"type_field" Fun.id
153153+ let type_field_codec =
154154+ Jsont.Object.map ~kind:"type_field" Fun.id
139155 |> Jsont.Object.opt_mem "type" Jsont.string ~enc:Fun.id
140156 |> Jsont.Object.finish
141157 in
142158143159 let dec json =
144160 match Jsont.Json.decode type_field_codec json with
145145- | Error _ | Ok None ->
161161+ | Error _ | Ok None -> (
146162 (* No type field, try as message *)
147147- (match Jsont.Json.decode Message.jsont json with
163163+ match Jsont.Json.decode Message.jsont json with
148164 | Ok msg -> Message msg
149165 | Error err -> failwith ("Failed to decode message: " ^ err))
150150- | Ok (Some typ) ->
166166+ | Ok (Some typ) -> (
151167 match typ with
152152- | "control_response" ->
153153- (match Jsont.Json.decode Sdk_control.control_response_jsont json with
168168+ | "control_response" -> (
169169+ match Jsont.Json.decode Sdk_control.control_response_jsont json with
154170 | Ok resp -> Control_response resp
155155- | Error err -> failwith ("Failed to decode control_response: " ^ err))
156156- | "control_request" ->
157157- (match Jsont.Json.decode Control_request.jsont json with
171171+ | Error err -> failwith ("Failed to decode control_response: " ^ err)
172172+ )
173173+ | "control_request" -> (
174174+ match Jsont.Json.decode Control_request.jsont json with
158175 | Ok req -> Control_request req
159159- | Error err -> failwith ("Failed to decode control_request: " ^ err))
160160- | "user" | "assistant" | "system" | "result" | _ ->
176176+ | Error err -> failwith ("Failed to decode control_request: " ^ err)
177177+ )
178178+ | "user" | "assistant" | "system" | "result" | _ -> (
161179 (* Message types *)
162162- (match Jsont.Json.decode Message.jsont json with
180180+ match Jsont.Json.decode Message.jsont json with
163181 | Ok msg -> Message msg
164164- | Error err -> failwith ("Failed to decode message: " ^ err))
182182+ | Error err -> failwith ("Failed to decode message: " ^ err)))
165183 in
166184167185 let enc = function
168168- | Message msg ->
169169- (match Jsont.Json.encode Message.jsont msg with
186186+ | Message msg -> (
187187+ match Jsont.Json.encode Message.jsont msg with
170188 | Ok json -> json
171189 | Error err -> failwith ("Failed to encode message: " ^ err))
172172- | Control_response resp ->
173173- (match Jsont.Json.encode Sdk_control.control_response_jsont resp with
190190+ | Control_response resp -> (
191191+ match Jsont.Json.encode Sdk_control.control_response_jsont resp with
174192 | Ok json -> json
175193 | Error err -> failwith ("Failed to encode control response: " ^ err))
176176- | Control_request req ->
177177- (match Jsont.Json.encode Control_request.jsont req with
194194+ | Control_request req -> (
195195+ match Jsont.Json.encode Control_request.jsont req with
178196 | Ok json -> json
179197 | Error err -> failwith ("Failed to encode control request: " ^ err))
180198 in
+5-5
lib/incoming.mli
···11(** Incoming messages from the Claude CLI.
2233- This module defines a discriminated union of all possible message types
44- that can be received from the Claude CLI, with a single jsont codec.
33+ This module defines a discriminated union of all possible message types that
44+ can be received from the Claude CLI, with a single jsont codec.
5566 The codec uses the "type" field to discriminate between message types:
77 - "user", "assistant", "system", "result" -> Message variant
···3939 | Hook_callback of Hook_callback.t
4040 | Unknown of string * Jsont.json
41414242- (** Full control request message *)
4342 type t
4343+ (** Full control request message *)
44444545 val request_id : t -> string
4646 val request : t -> request
···5454 | Control_request of Control_request.t
55555656val jsont : t Jsont.t
5757-(** Codec for incoming messages. Uses the "type" field to discriminate.
5858- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
5757+(** Codec for incoming messages. Uses the "type" field to discriminate. Use
5858+ [Jsont.pp_value jsont ()] for pretty-printing. *)
+267-221
lib/message.ml
···11let src = Logs.Src.create "claude.message" ~doc:"Claude messages"
22-module Log = (val Logs.src_log src : Logs.LOG)
3233+module Log = (val Logs.src_log src : Logs.LOG)
4455module User = struct
66- type content =
77- | String of string
88- | Blocks of Content_block.t list
99-1010- type t = {
1111- content : content;
1212- unknown : Unknown.t;
1313- }
66+ type content = String of string | Blocks of Content_block.t list
77+ type t = { content : content; unknown : Unknown.t }
148159 let create_string s = { content = String s; unknown = Unknown.empty }
1616- let create_blocks blocks = { content = Blocks blocks; unknown = Unknown.empty }
1010+1111+ let create_blocks blocks =
1212+ { content = Blocks blocks; unknown = Unknown.empty }
17131814 let create_with_tool_result ~tool_use_id ~content ?is_error () =
1919- let tool_result = Content_block.tool_result ~tool_use_id ~content ?is_error () in
2020- { content = Blocks [tool_result]; unknown = Unknown.empty }
1515+ let tool_result =
1616+ Content_block.tool_result ~tool_use_id ~content ?is_error ()
1717+ in
1818+ { content = Blocks [ tool_result ]; unknown = Unknown.empty }
21192220 let create_mixed ~text ~tool_results =
2321 let blocks =
2424- let text_blocks = match text with
2525- | Some t -> [Content_block.text t]
2626- | None -> []
2222+ let text_blocks =
2323+ match text with Some t -> [ Content_block.text t ] | None -> []
2424+ in
2525+ let tool_blocks =
2626+ List.map
2727+ (fun (tool_use_id, content, is_error) ->
2828+ Content_block.tool_result ~tool_use_id ~content ?is_error ())
2929+ tool_results
2730 in
2828- let tool_blocks = List.map (fun (tool_use_id, content, is_error) ->
2929- Content_block.tool_result ~tool_use_id ~content ?is_error ()
3030- ) tool_results in
3131 text_blocks @ tool_blocks
3232 in
3333 { content = Blocks blocks; unknown = Unknown.empty }
···3535 let make content unknown = { content; unknown }
3636 let content t = t.content
3737 let unknown t = t.unknown
3838+ let as_text t = match t.content with String s -> Some s | Blocks _ -> None
38393939- let as_text t = match t.content with
4040- | String s -> Some s
4141- | Blocks _ -> None
4242-4343- let get_blocks t = match t.content with
4444- | String s -> [Content_block.text s]
4040+ let get_blocks t =
4141+ match t.content with
4242+ | String s -> [ Content_block.text s ]
4543 | Blocks blocks -> blocks
46444745 (* Decode content from json value *)
4848- let decode_content json = match json with
4646+ let decode_content json =
4747+ match json with
4948 | Jsont.String (s, _) -> String s
5049 | Jsont.Array (items, _) ->
5151- let blocks = List.map (fun j ->
5252- match Jsont.Json.decode Content_block.jsont j with
5353- | Ok b -> b
5454- | Error msg -> failwith ("Invalid content block: " ^ msg)
5555- ) items in
5050+ let blocks =
5151+ List.map
5252+ (fun j ->
5353+ Jsont.Json.decode Content_block.jsont j
5454+ |> Err.get_ok ~msg:"Invalid content block: ")
5555+ items
5656+ in
5657 Blocks blocks
5758 | _ -> failwith "Content must be string or array"
5859···6061 let encode_content = function
6162 | String s -> Jsont.String (s, Jsont.Meta.none)
6263 | Blocks blocks ->
6363- let jsons = List.map (fun b ->
6464- match Jsont.Json.encode Content_block.jsont b with
6565- | Ok j -> j
6666- | Error msg -> failwith ("encode_content: " ^ msg)
6767- ) blocks in
6464+ let jsons =
6565+ List.map
6666+ (fun b ->
6767+ Jsont.Json.encode Content_block.jsont b
6868+ |> Err.get_ok ~msg:"encode_content: ")
6969+ blocks
7070+ in
6871 Jsont.Array (jsons, Jsont.Meta.none)
69727073 let jsont : t Jsont.t =
7174 Jsont.Object.map ~kind:"User" (fun json_content unknown ->
7272- let content = decode_content json_content in
7373- make content unknown
7474- )
7575- |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t -> encode_content (content t))
7575+ let content = decode_content json_content in
7676+ make content unknown)
7777+ |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
7878+ encode_content (content t))
7679 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
7780 |> Jsont.Object.finish
7881···99102100103 let to_json t =
101104 let content_json = encode_content t.content in
102102- let wire = Wire.{
103103- type_ = "user";
104104- message = { role = "user"; content = content_json }
105105- } in
106106- match Jsont.Json.encode Wire.outer_jsont wire with
107107- | Ok json -> json
108108- | Error msg -> failwith ("User.to_json: " ^ msg)
105105+ let wire =
106106+ Wire.
107107+ { type_ = "user"; message = { role = "user"; content = content_json } }
108108+ in
109109+ Jsont.Json.encode Wire.outer_jsont wire |> Err.get_ok ~msg:"User.to_json: "
109110110111 (* Jsont codec for parsing incoming user messages from CLI *)
111112 let incoming_jsont : t Jsont.t =
112113 let message_jsont =
113114 Jsont.Object.map ~kind:"UserMessage" (fun json_content ->
114114- let content = decode_content json_content in
115115- { content; unknown = Unknown.empty }
116116- )
117117- |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t -> encode_content (content t))
115115+ let content = decode_content json_content in
116116+ { content; unknown = Unknown.empty })
117117+ |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
118118+ encode_content (content t))
118119 |> Jsont.Object.finish
119120 in
120121 Jsont.Object.map ~kind:"UserEnvelope" Fun.id
···122123 |> Jsont.Object.finish
123124124125 let of_json json =
125125- match Jsont.Json.decode incoming_jsont json with
126126- | Ok v -> v
127127- | Error msg -> raise (Invalid_argument ("User.of_json: " ^ msg))
126126+ Jsont.Json.decode incoming_jsont json |> Err.get_ok' ~msg:"User.of_json: "
128127end
129128130129module Assistant = struct
131131- type error = [
132132- | `Authentication_failed
130130+ type error =
131131+ [ `Authentication_failed
133132 | `Billing_error
134133 | `Rate_limit
135134 | `Invalid_request
136135 | `Server_error
137137- | `Unknown
138138- ]
136136+ | `Unknown ]
139137140138 let error_to_string = function
141139 | `Authentication_failed -> "authentication_failed"
···154152 | "unknown" | _ -> `Unknown
155153156154 let error_jsont : error Jsont.t =
157157- Jsont.enum [
158158- ("authentication_failed", `Authentication_failed);
159159- ("billing_error", `Billing_error);
160160- ("rate_limit", `Rate_limit);
161161- ("invalid_request", `Invalid_request);
162162- ("server_error", `Server_error);
163163- ("unknown", `Unknown);
164164- ]
155155+ Jsont.enum
156156+ [
157157+ ("authentication_failed", `Authentication_failed);
158158+ ("billing_error", `Billing_error);
159159+ ("rate_limit", `Rate_limit);
160160+ ("invalid_request", `Invalid_request);
161161+ ("server_error", `Server_error);
162162+ ("unknown", `Unknown);
163163+ ]
165164166165 type t = {
167166 content : Content_block.t list;
···170169 unknown : Unknown.t;
171170 }
172171173173- let create ~content ~model ?error () = { content; model; error; unknown = Unknown.empty }
172172+ let create ~content ~model ?error () =
173173+ { content; model; error; unknown = Unknown.empty }
174174+174175 let make content model error unknown = { content; model; error; unknown }
175176 let content t = t.content
176177 let model t = t.model
···178179 let unknown t = t.unknown
179180180181 let get_text_blocks t =
181181- List.filter_map (function
182182- | Content_block.Text text -> Some (Content_block.Text.text text)
183183- | _ -> None
184184- ) t.content
182182+ List.filter_map
183183+ (function
184184+ | Content_block.Text text -> Some (Content_block.Text.text text)
185185+ | _ -> None)
186186+ t.content
185187186188 let get_tool_uses t =
187187- List.filter_map (function
188188- | Content_block.Tool_use tool -> Some tool
189189- | _ -> None
190190- ) t.content
189189+ List.filter_map
190190+ (function Content_block.Tool_use tool -> Some tool | _ -> None)
191191+ t.content
191192192193 let get_thinking t =
193193- List.filter_map (function
194194- | Content_block.Thinking thinking -> Some thinking
195195- | _ -> None
196196- ) t.content
194194+ List.filter_map
195195+ (function Content_block.Thinking thinking -> Some thinking | _ -> None)
196196+ t.content
197197198198 let has_tool_use t =
199199- List.exists (function
200200- | Content_block.Tool_use _ -> true
201201- | _ -> false
202202- ) t.content
199199+ List.exists
200200+ (function Content_block.Tool_use _ -> true | _ -> false)
201201+ t.content
203202204204- let combined_text t =
205205- String.concat "\n" (get_text_blocks t)
203203+ let combined_text t = String.concat "\n" (get_text_blocks t)
206204207205 let jsont : t Jsont.t =
208206 Jsont.Object.map ~kind:"Assistant" make
···213211 |> Jsont.Object.finish
214212215213 let encode_content_blocks blocks =
216216- let jsons = List.map (fun b ->
217217- match Jsont.Json.encode Content_block.jsont b with
218218- | Ok j -> j
219219- | Error msg -> failwith ("encode_content_blocks: " ^ msg)
220220- ) blocks in
214214+ let jsons =
215215+ List.map
216216+ (fun b ->
217217+ Jsont.Json.encode Content_block.jsont b
218218+ |> Err.get_ok ~msg:"encode_content_blocks: ")
219219+ blocks
220220+ in
221221 Jsont.Array (jsons, Jsont.Meta.none)
222222223223 (** Wire-format codec for outgoing assistant messages. *)
···227227 wire_model : string;
228228 wire_error : string option;
229229 }
230230+230231 type outer = { wire_type : string; wire_message : inner }
231232232233 let inner_jsont : inner Jsont.t =
233233- let make wire_content wire_model wire_error = { wire_content; wire_model; wire_error } in
234234+ let make wire_content wire_model wire_error =
235235+ { wire_content; wire_model; wire_error }
236236+ in
234237 Jsont.Object.map ~kind:"AssistantMessageInner" make
235238 |> Jsont.Object.mem "content" Jsont.json ~enc:(fun r -> r.wire_content)
236239 |> Jsont.Object.mem "model" Jsont.string ~enc:(fun r -> r.wire_model)
···246249 end
247250248251 let to_json t =
249249- let wire = Wire.{
250250- wire_type = "assistant";
251251- wire_message = {
252252- wire_content = encode_content_blocks t.content;
253253- wire_model = t.model;
254254- wire_error = Option.map error_to_string t.error;
255255- }
256256- } in
257257- match Jsont.Json.encode Wire.outer_jsont wire with
258258- | Ok json -> json
259259- | Error msg -> failwith ("Assistant.to_json: " ^ msg)
252252+ let wire =
253253+ Wire.
254254+ {
255255+ wire_type = "assistant";
256256+ wire_message =
257257+ {
258258+ wire_content = encode_content_blocks t.content;
259259+ wire_model = t.model;
260260+ wire_error = Option.map error_to_string t.error;
261261+ };
262262+ }
263263+ in
264264+ Jsont.Json.encode Wire.outer_jsont wire
265265+ |> Err.get_ok ~msg:"Assistant.to_json: "
260266261267 (* Jsont codec for parsing incoming assistant messages from CLI *)
262268 let incoming_jsont : t Jsont.t =
···265271 |> Jsont.Object.finish
266272267273 let of_json json =
268268- match Jsont.Json.decode incoming_jsont json with
269269- | Ok v -> v
270270- | Error msg -> raise (Invalid_argument ("Assistant.of_json: " ^ msg))
274274+ Jsont.Json.decode incoming_jsont json
275275+ |> Err.get_ok' ~msg:"Assistant.of_json: "
271276end
272277273278module System = struct
···280285 unknown : Unknown.t;
281286 }
282287283283- type error = {
284284- error : string;
285285- unknown : Unknown.t;
286286- }
287287-288288- type other = {
289289- subtype : string;
290290- unknown : Unknown.t;
291291- }
292292-293293- type t =
294294- | Init of init
295295- | Error of error
296296- | Other of other
288288+ type error = { error : string; unknown : Unknown.t }
289289+ type other = { subtype : string; unknown : Unknown.t }
290290+ type t = Init of init | Error of error | Other of other
297291298292 (* Accessors *)
299293 let session_id = function Init i -> i.session_id | _ -> None
300294 let model = function Init i -> i.model | _ -> None
301295 let cwd = function Init i -> i.cwd | _ -> None
302296 let error_msg = function Error e -> Some e.error | _ -> None
303303- let subtype = function Init _ -> "init" | Error _ -> "error" | Other o -> o.subtype
297297+298298+ let subtype = function
299299+ | Init _ -> "init"
300300+ | Error _ -> "error"
301301+ | Other o -> o.subtype
302302+304303 let unknown = function
305304 | Init i -> i.unknown
306305 | Error e -> e.unknown
···310309 let init ?session_id ?model ?cwd () =
311310 Init { session_id; model; cwd; unknown = Unknown.empty }
312311313313- let error ~error =
314314- Error { error; unknown = Unknown.empty }
315315-316316- let other ~subtype =
317317- Other { subtype; unknown = Unknown.empty }
312312+ let error ~error = Error { error; unknown = Unknown.empty }
313313+ let other ~subtype = Other { subtype; unknown = Unknown.empty }
318314319315 (* Individual record codecs *)
320316 let init_jsont : init Jsont.t =
321321- let make session_id model cwd unknown : init = { session_id; model; cwd; unknown } in
317317+ let make session_id model cwd unknown : init =
318318+ { session_id; model; cwd; unknown }
319319+ in
322320 Jsont.Object.map ~kind:"SystemInit" make
323323- |> Jsont.Object.opt_mem "session_id" Jsont.string ~enc:(fun (r : init) -> r.session_id)
324324- |> Jsont.Object.opt_mem "model" Jsont.string ~enc:(fun (r : init) -> r.model)
321321+ |> Jsont.Object.opt_mem "session_id" Jsont.string ~enc:(fun (r : init) ->
322322+ r.session_id)
323323+ |> Jsont.Object.opt_mem "model" Jsont.string ~enc:(fun (r : init) ->
324324+ r.model)
325325 |> Jsont.Object.opt_mem "cwd" Jsont.string ~enc:(fun (r : init) -> r.cwd)
326326- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : init) -> r.unknown)
326326+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : init) ->
327327+ r.unknown)
327328 |> Jsont.Object.finish
328329329330 let error_jsont : error Jsont.t =
330331 let make err unknown : error = { error = err; unknown } in
331332 Jsont.Object.map ~kind:"SystemError" make
332333 |> Jsont.Object.mem "error" Jsont.string ~enc:(fun (r : error) -> r.error)
333333- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : error) -> r.unknown)
334334+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : error) ->
335335+ r.unknown)
334336 |> Jsont.Object.finish
335337336338 (* Main codec using case_mem for "subtype" discriminator *)
337339 let jsont : t Jsont.t =
338338- let case_init = Jsont.Object.Case.map "init" init_jsont ~dec:(fun v -> Init v) in
339339- let case_error = Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v) in
340340+ let case_init =
341341+ Jsont.Object.Case.map "init" init_jsont ~dec:(fun v -> Init v)
342342+ in
343343+ let case_error =
344344+ Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v)
345345+ in
340346 let case_other tag =
341347 (* For unknown subtypes, create Other with the tag as subtype *)
342348 let other_codec : other Jsont.t =
343349 let make unknown : other = { subtype = tag; unknown } in
344350 Jsont.Object.map ~kind:"SystemOther" make
345345- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : other) -> r.unknown)
351351+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : other) ->
352352+ r.unknown)
346353 |> Jsont.Object.finish
347354 in
348355 Jsont.Object.Case.map tag other_codec ~dec:(fun v -> Other v)
···352359 | Error v -> Jsont.Object.Case.value case_error v
353360 | Other v -> Jsont.Object.Case.value (case_other v.subtype) v
354361 in
355355- let cases = Jsont.Object.Case.[
356356- make case_init;
357357- make case_error;
358358- ] in
362362+ let cases = Jsont.Object.Case.[ make case_init; make case_error ] in
359363 Jsont.Object.map ~kind:"System" Fun.id
360364 |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
361361- ~tag_to_string:Fun.id ~tag_compare:String.compare
365365+ ~tag_to_string:Fun.id ~tag_compare:String.compare
362366 |> Jsont.Object.finish
363367364368 let to_json t =
365365- match Jsont.Json.encode jsont t with
366366- | Ok json -> json
367367- | Error msg -> failwith ("System.to_json: " ^ msg)
369369+ Jsont.Json.encode jsont t |> Err.get_ok ~msg:"System.to_json: "
368370369371 let of_json json =
370370- match Jsont.Json.decode jsont json with
371371- | Ok v -> v
372372- | Error msg -> raise (Invalid_argument ("System.of_json: " ^ msg))
372372+ Jsont.Json.decode jsont json |> Err.get_ok' ~msg:"System.of_json: "
373373end
374374375375module Result = struct
···383383 unknown : Unknown.t;
384384 }
385385386386- let make input_tokens output_tokens total_tokens
387387- cache_creation_input_tokens cache_read_input_tokens unknown =
388388- { input_tokens; output_tokens; total_tokens;
389389- cache_creation_input_tokens; cache_read_input_tokens; unknown }
386386+ let make input_tokens output_tokens total_tokens cache_creation_input_tokens
387387+ cache_read_input_tokens unknown =
388388+ {
389389+ input_tokens;
390390+ output_tokens;
391391+ total_tokens;
392392+ cache_creation_input_tokens;
393393+ cache_read_input_tokens;
394394+ unknown;
395395+ }
390396391397 let create ?input_tokens ?output_tokens ?total_tokens
392392- ?cache_creation_input_tokens ?cache_read_input_tokens () =
393393- { input_tokens; output_tokens; total_tokens;
394394- cache_creation_input_tokens; cache_read_input_tokens;
395395- unknown = Unknown.empty }
398398+ ?cache_creation_input_tokens ?cache_read_input_tokens () =
399399+ {
400400+ input_tokens;
401401+ output_tokens;
402402+ total_tokens;
403403+ cache_creation_input_tokens;
404404+ cache_read_input_tokens;
405405+ unknown = Unknown.empty;
406406+ }
396407397408 let input_tokens t = t.input_tokens
398409 let output_tokens t = t.output_tokens
···406417 |> Jsont.Object.opt_mem "input_tokens" Jsont.int ~enc:input_tokens
407418 |> Jsont.Object.opt_mem "output_tokens" Jsont.int ~enc:output_tokens
408419 |> Jsont.Object.opt_mem "total_tokens" Jsont.int ~enc:total_tokens
409409- |> Jsont.Object.opt_mem "cache_creation_input_tokens" Jsont.int ~enc:cache_creation_input_tokens
410410- |> Jsont.Object.opt_mem "cache_read_input_tokens" Jsont.int ~enc:cache_read_input_tokens
420420+ |> Jsont.Object.opt_mem "cache_creation_input_tokens" Jsont.int
421421+ ~enc:cache_creation_input_tokens
422422+ |> Jsont.Object.opt_mem "cache_read_input_tokens" Jsont.int
423423+ ~enc:cache_read_input_tokens
411424 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
412425 |> Jsont.Object.finish
413426···419432 max 0 (input - cached)
420433421434 let total_cost_estimate t ~input_price ~output_price =
422422- match t.input_tokens, t.output_tokens with
435435+ match (t.input_tokens, t.output_tokens) with
423436 | Some input, Some output ->
424437 let input_cost = float_of_int input *. input_price /. 1_000_000. in
425438 let output_cost = float_of_int output *. output_price /. 1_000_000. in
···443456444457 let create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
445458 ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
446446- { subtype; duration_ms; duration_api_ms; is_error; num_turns;
447447- session_id; total_cost_usd; usage; result; structured_output; unknown = Unknown.empty }
459459+ {
460460+ subtype;
461461+ duration_ms;
462462+ duration_api_ms;
463463+ is_error;
464464+ num_turns;
465465+ session_id;
466466+ total_cost_usd;
467467+ usage;
468468+ result;
469469+ structured_output;
470470+ unknown = Unknown.empty;
471471+ }
448472449449- let make subtype duration_ms duration_api_ms is_error num_turns
450450- session_id total_cost_usd usage result structured_output unknown =
451451- { subtype; duration_ms; duration_api_ms; is_error; num_turns;
452452- session_id; total_cost_usd; usage; result; structured_output; unknown }
473473+ let make subtype duration_ms duration_api_ms is_error num_turns session_id
474474+ total_cost_usd usage result structured_output unknown =
475475+ {
476476+ subtype;
477477+ duration_ms;
478478+ duration_api_ms;
479479+ is_error;
480480+ num_turns;
481481+ session_id;
482482+ total_cost_usd;
483483+ usage;
484484+ result;
485485+ structured_output;
486486+ unknown;
487487+ }
453488454489 let subtype t = t.subtype
455490 let duration_ms t = t.duration_ms
···474509 |> Jsont.Object.opt_mem "total_cost_usd" Jsont.number ~enc:total_cost_usd
475510 |> Jsont.Object.opt_mem "usage" Usage.jsont ~enc:usage
476511 |> Jsont.Object.opt_mem "result" Jsont.string ~enc:result
477477- |> Jsont.Object.opt_mem "structured_output" Jsont.json ~enc:structured_output
512512+ |> Jsont.Object.opt_mem "structured_output" Jsont.json
513513+ ~enc:structured_output
478514 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
479515 |> Jsont.Object.finish
480516···496532497533 let jsont : wire Jsont.t =
498534 let make type_ subtype duration_ms duration_api_ms is_error num_turns
499499- session_id total_cost_usd usage result structured_output =
500500- { type_; subtype; duration_ms; duration_api_ms; is_error; num_turns;
501501- session_id; total_cost_usd; usage; result; structured_output }
535535+ session_id total_cost_usd usage result structured_output =
536536+ {
537537+ type_;
538538+ subtype;
539539+ duration_ms;
540540+ duration_api_ms;
541541+ is_error;
542542+ num_turns;
543543+ session_id;
544544+ total_cost_usd;
545545+ usage;
546546+ result;
547547+ structured_output;
548548+ }
502549 in
503550 Jsont.Object.map ~kind:"ResultWire" make
504551 |> Jsont.Object.mem "type" Jsont.string ~enc:(fun r -> r.type_)
505552 |> Jsont.Object.mem "subtype" Jsont.string ~enc:(fun r -> r.subtype)
506553 |> Jsont.Object.mem "duration_ms" Jsont.int ~enc:(fun r -> r.duration_ms)
507507- |> Jsont.Object.mem "duration_api_ms" Jsont.int ~enc:(fun r -> r.duration_api_ms)
554554+ |> Jsont.Object.mem "duration_api_ms" Jsont.int ~enc:(fun r ->
555555+ r.duration_api_ms)
508556 |> Jsont.Object.mem "is_error" Jsont.bool ~enc:(fun r -> r.is_error)
509557 |> Jsont.Object.mem "num_turns" Jsont.int ~enc:(fun r -> r.num_turns)
510558 |> Jsont.Object.mem "session_id" Jsont.string ~enc:(fun r -> r.session_id)
511511- |> Jsont.Object.opt_mem "total_cost_usd" Jsont.number ~enc:(fun r -> r.total_cost_usd)
559559+ |> Jsont.Object.opt_mem "total_cost_usd" Jsont.number ~enc:(fun r ->
560560+ r.total_cost_usd)
512561 |> Jsont.Object.opt_mem "usage" Jsont.json ~enc:(fun r -> r.usage)
513562 |> Jsont.Object.opt_mem "result" Jsont.string ~enc:(fun r -> r.result)
514514- |> Jsont.Object.opt_mem "structured_output" Jsont.json ~enc:(fun r -> r.structured_output)
563563+ |> Jsont.Object.opt_mem "structured_output" Jsont.json ~enc:(fun r ->
564564+ r.structured_output)
515565 |> Jsont.Object.finish
516566 end
517567518568 let to_json t =
519519- let usage_json = Option.map (fun u ->
520520- match Jsont.Json.encode Usage.jsont u with
521521- | Ok j -> j
522522- | Error msg -> failwith ("Result.to_json: usage: " ^ msg)
523523- ) t.usage in
524524- let wire = Wire.{
525525- type_ = "result";
526526- subtype = t.subtype;
527527- duration_ms = t.duration_ms;
528528- duration_api_ms = t.duration_api_ms;
529529- is_error = t.is_error;
530530- num_turns = t.num_turns;
531531- session_id = t.session_id;
532532- total_cost_usd = t.total_cost_usd;
533533- usage = usage_json;
534534- result = t.result;
535535- structured_output = t.structured_output;
536536- } in
537537- match Jsont.Json.encode Wire.jsont wire with
538538- | Ok json -> json
539539- | Error msg -> failwith ("Result.to_json: " ^ msg)
569569+ let usage_json =
570570+ t.usage
571571+ |> Option.map (fun u ->
572572+ Jsont.Json.encode Usage.jsont u
573573+ |> Err.get_ok ~msg:"Result.to_json: usage: ")
574574+ in
575575+ let wire =
576576+ Wire.
577577+ {
578578+ type_ = "result";
579579+ subtype = t.subtype;
580580+ duration_ms = t.duration_ms;
581581+ duration_api_ms = t.duration_api_ms;
582582+ is_error = t.is_error;
583583+ num_turns = t.num_turns;
584584+ session_id = t.session_id;
585585+ total_cost_usd = t.total_cost_usd;
586586+ usage = usage_json;
587587+ result = t.result;
588588+ structured_output = t.structured_output;
589589+ }
590590+ in
591591+ Jsont.Json.encode Wire.jsont wire |> Err.get_ok ~msg:"Result.to_json: "
540592541593 let of_json json =
542542- match Jsont.Json.decode jsont json with
543543- | Ok v -> v
544544- | Error msg -> raise (Invalid_argument ("Result.of_json: " ^ msg))
594594+ Jsont.Json.decode jsont json |> Err.get_ok' ~msg:"Result.of_json: "
545595end
546596547597type t =
···552602553603let user_string s = User (User.create_string s)
554604let user_blocks blocks = User (User.create_blocks blocks)
605605+555606let user_with_tool_result ~tool_use_id ~content ?is_error () =
556607 User (User.create_with_tool_result ~tool_use_id ~content ?is_error ())
557608558558-let assistant ~content ~model ?error () = Assistant (Assistant.create ~content ~model ?error ())
609609+let assistant ~content ~model ?error () =
610610+ Assistant (Assistant.create ~content ~model ?error ())
611611+559612let assistant_text ~text ~model ?error () =
560560- Assistant (Assistant.create ~content:[Content_block.text text] ~model ?error ())
613613+ Assistant
614614+ (Assistant.create ~content:[ Content_block.text text ] ~model ?error ())
561615562562-let system_init ~session_id =
563563- System (System.init ~session_id ())
564564-let system_error ~error =
565565- System (System.error ~error)
616616+let system_init ~session_id = System (System.init ~session_id ())
617617+let system_error ~error = System (System.error ~error)
566618567619let result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
568620 ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
569569- Result (Result.create ~subtype ~duration_ms ~duration_api_ms ~is_error
570570- ~num_turns ~session_id ?total_cost_usd ?usage ?result ?structured_output ())
621621+ Result
622622+ (Result.create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
623623+ ~session_id ?total_cost_usd ?usage ?result ?structured_output ())
571624572625let to_json = function
573626 | User t -> User.to_json t
···580633let jsont : t Jsont.t =
581634 let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
582635 let case_user = case_map "user" User.incoming_jsont (fun v -> User v) in
583583- let case_assistant = case_map "assistant" Assistant.incoming_jsont (fun v -> Assistant v) in
636636+ let case_assistant =
637637+ case_map "assistant" Assistant.incoming_jsont (fun v -> Assistant v)
638638+ in
584639 let case_system = case_map "system" System.jsont (fun v -> System v) in
585640 let case_result = case_map "result" Result.jsont (fun v -> Result v) in
586641 let enc_case = function
···589644 | System v -> Jsont.Object.Case.value case_system v
590645 | Result v -> Jsont.Object.Case.value case_result v
591646 in
592592- let cases = Jsont.Object.Case.[
593593- make case_user;
594594- make case_assistant;
595595- make case_system;
596596- make case_result
597597- ] in
647647+ let cases =
648648+ Jsont.Object.Case.
649649+ [
650650+ make case_user; make case_assistant; make case_system; make case_result;
651651+ ]
652652+ in
598653 Jsont.Object.map ~kind:"Message" Fun.id
599654 |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
600600- ~tag_to_string:Fun.id ~tag_compare:String.compare
655655+ ~tag_to_string:Fun.id ~tag_compare:String.compare
601656 |> Jsont.Object.finish
602657603658let of_json json =
604604- match Jsont.Json.decode jsont json with
605605- | Ok v -> v
606606- | Error msg -> raise (Invalid_argument ("Message.of_json: " ^ msg))
659659+ Jsont.Json.decode jsont json |> Err.get_ok' ~msg:"Message.of_json: "
607660608661let is_user = function User _ -> true | _ -> false
609662let is_assistant = function Assistant _ -> true | _ -> false
···617670618671let extract_text = function
619672 | User u -> User.as_text u
620620- | Assistant a ->
673673+ | Assistant a ->
621674 let text = Assistant.combined_text a in
622675 if text = "" then None else Some text
623676 | _ -> None
···632685 | _ -> None
633686634687let pp = Jsont.pp_value jsont ()
635635-636636-let log_received t =
637637- Log.info (fun m -> m "← %a" pp t)
638638-639639-let log_sending t =
640640- Log.info (fun m -> m "→ %a" pp t)
641641-642642-let log_error msg t =
643643- Log.err (fun m -> m "%s: %a" msg pp t)
644644-688688+let log_received t = Log.info (fun m -> m "← %a" pp t)
689689+let log_sending t = Log.info (fun m -> m "→ %a" pp t)
690690+let log_error msg t = Log.err (fun m -> m "%s: %a" msg pp t)
+70-56
lib/message.mli
···44 received from Claude, including user input, assistant responses, system
55 messages, and result metadata. *)
6677-(** The log source for message operations *)
87val src : Logs.Src.t
88+(** The log source for message operations *)
991010(** {1 User Messages} *)
11111212module User : sig
1313 (** Messages sent by the user. *)
14141515+ (** The content of a user message. *)
1516 type content =
1617 | String of string (** Simple text message *)
1717- | Blocks of Content_block.t list (** Complex message with multiple content blocks *)
1818- (** The content of a user message. *)
1818+ | Blocks of Content_block.t list
1919+ (** Complex message with multiple content blocks *)
19202021 type t
2122 (** The type of user messages. *)
···3031 (** [create_blocks blocks] creates a user message with content blocks. *)
31323233 val create_with_tool_result :
3333- tool_use_id:string ->
3434- content:string ->
3535- ?is_error:bool ->
3636- unit -> t
3737- (** [create_with_tool_result ~tool_use_id ~content ?is_error ()] creates a user
3838- message containing a tool result. *)
3434+ tool_use_id:string -> content:string -> ?is_error:bool -> unit -> t
3535+ (** [create_with_tool_result ~tool_use_id ~content ?is_error ()] creates a
3636+ user message containing a tool result. *)
39374040- val create_mixed : text:string option -> tool_results:(string * string * bool option) list -> t
4141- (** [create_mixed ?text ~tool_results] creates a user message with optional text
4242- and tool results. Each tool result is (tool_use_id, content, is_error). *)
3838+ val create_mixed :
3939+ text:string option -> tool_results:(string * string * bool option) list -> t
4040+ (** [create_mixed ?text ~tool_results] creates a user message with optional
4141+ text and tool results. Each tool result is (tool_use_id, content,
4242+ is_error). *)
43434444 val content : t -> content
4545 (** [content t] returns the content of the user message. *)
···4848 (** [unknown t] returns the unknown fields preserved from JSON. *)
49495050 val as_text : t -> string option
5151- (** [as_text t] returns the text content if the message is a simple string, None otherwise. *)
5151+ (** [as_text t] returns the text content if the message is a simple string,
5252+ None otherwise. *)
52535354 val get_blocks : t -> Content_block.t list
5454- (** [get_blocks t] returns the content blocks, or a single text block if it's a string message. *)
5555+ (** [get_blocks t] returns the content blocks, or a single text block if it's
5656+ a string message. *)
55575658 val to_json : t -> Jsont.json
5759 (** [to_json t] converts the user message to its JSON representation. *)
···6668module Assistant : sig
6769 (** Messages from Claude assistant. *)
68706969- type error = [
7070- | `Authentication_failed (** Authentication with Claude API failed *)
7171- | `Billing_error (** Billing or account issue *)
7272- | `Rate_limit (** Rate limit exceeded *)
7373- | `Invalid_request (** Request was invalid *)
7474- | `Server_error (** Internal server error *)
7575- | `Unknown (** Unknown error type *)
7676- ]
7171+ type error =
7272+ [ `Authentication_failed (** Authentication with Claude API failed *)
7373+ | `Billing_error (** Billing or account issue *)
7474+ | `Rate_limit (** Rate limit exceeded *)
7575+ | `Invalid_request (** Request was invalid *)
7676+ | `Server_error (** Internal server error *)
7777+ | `Unknown (** Unknown error type *) ]
7778 (** The type of assistant message errors based on Python SDK error types. *)
78797980 val error_to_string : error -> string
8081 (** [error_to_string err] converts an error to its string representation. *)
81828283 val error_of_string : string -> error
8383- (** [error_of_string s] parses an error string. Unknown strings become [`Unknown]. *)
8484+ (** [error_of_string s] parses an error string. Unknown strings become
8585+ [`Unknown]. *)
84868587 type t
8688 (** The type of assistant messages. *)
···8890 val jsont : t Jsont.t
8991 (** [jsont] is the Jsont codec for assistant messages. *)
90929191- val create : content:Content_block.t list -> model:string -> ?error:error -> unit -> t
9393+ val create :
9494+ content:Content_block.t list -> model:string -> ?error:error -> unit -> t
9295 (** [create ~content ~model ?error ()] creates an assistant message.
9396 @param content List of content blocks in the response
9497 @param model The model identifier used for the response
···101104 (** [model t] returns the model identifier. *)
102105103106 val error : t -> error option
104104- (** [error t] returns the optional error that occurred during message generation. *)
107107+ (** [error t] returns the optional error that occurred during message
108108+ generation. *)
105109106110 val unknown : t -> Unknown.t
107111 (** [unknown t] returns the unknown fields preserved from JSON. *)
···116120 (** [get_thinking t] extracts all thinking blocks from the message. *)
117121118122 val has_tool_use : t -> bool
119119- (** [has_tool_use t] returns true if the message contains any tool use blocks. *)
123123+ (** [has_tool_use t] returns true if the message contains any tool use blocks.
124124+ *)
120125121126 val combined_text : t -> string
122127 (** [combined_text t] concatenates all text blocks into a single string. *)
···147152 }
148153 (** Init message fields. *)
149154150150- type error = {
151151- error : string;
152152- unknown : Unknown.t;
153153- }
155155+ type error = { error : string; unknown : Unknown.t }
154156 (** Error message fields. *)
155157156156- type other = {
157157- subtype : string;
158158- unknown : Unknown.t;
159159- }
158158+ type other = { subtype : string; unknown : Unknown.t }
160159 (** Unknown subtype fields. *)
161160162161 type t =
163162 | Init of init
164163 | Error of error
165165- | Other of other
166166- (** The type of system messages. *)
164164+ | Other of other (** The type of system messages. *)
167165168166 val jsont : t Jsont.t
169167 (** [jsont] is the Jsont codec for system messages. *)
···229227 ?total_tokens:int ->
230228 ?cache_creation_input_tokens:int ->
231229 ?cache_read_input_tokens:int ->
232232- unit -> t
233233- (** [create ?input_tokens ?output_tokens ?total_tokens ?cache_creation_input_tokens
234234- ?cache_read_input_tokens ()] creates usage statistics. *)
230230+ unit ->
231231+ t
232232+ (** [create ?input_tokens ?output_tokens ?total_tokens
233233+ ?cache_creation_input_tokens ?cache_read_input_tokens ()] creates usage
234234+ statistics. *)
235235236236 val input_tokens : t -> int option
237237 (** [input_tokens t] returns the number of input tokens used. *)
···252252 (** [unknown t] returns the unknown fields preserved from JSON. *)
253253254254 val effective_input_tokens : t -> int
255255- (** [effective_input_tokens t] returns input tokens minus cached tokens, or 0 if not available. *)
255255+ (** [effective_input_tokens t] returns input tokens minus cached tokens, or
256256+ 0 if not available. *)
256257257257- val total_cost_estimate : t -> input_price:float -> output_price:float -> float option
258258- (** [total_cost_estimate t ~input_price ~output_price] estimates the cost based on token
259259- prices per million tokens. Returns None if token counts are not available. *)
258258+ val total_cost_estimate :
259259+ t -> input_price:float -> output_price:float -> float option
260260+ (** [total_cost_estimate t ~input_price ~output_price] estimates the cost
261261+ based on token prices per million tokens. Returns None if token counts
262262+ are not available. *)
260263 end
261264262265 type t
···276279 ?usage:Usage.t ->
277280 ?result:string ->
278281 ?structured_output:Jsont.json ->
279279- unit -> t
282282+ unit ->
283283+ t
280284 (** [create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
281281- ~session_id ?total_cost_usd ?usage ?result ()] creates a result message.
285285+ ~session_id ?total_cost_usd ?usage ?result ()] creates a result message.
282286 @param subtype The subtype of the result
283287 @param duration_ms Total duration in milliseconds
284288 @param duration_api_ms API duration in milliseconds
···338342 | Assistant of Assistant.t
339343 | System of System.t
340344 | Result of Result.t
341341-(** The type of messages, which can be user, assistant, system, or result. *)
345345+ (** The type of messages, which can be user, assistant, system, or result.
346346+ *)
342347343348val jsont : t Jsont.t
344349(** [jsont] is the Jsont codec for messages. *)
···349354val user_blocks : Content_block.t list -> t
350355(** [user_blocks blocks] creates a user message with content blocks. *)
351356352352-val user_with_tool_result : tool_use_id:string -> content:string -> ?is_error:bool -> unit -> t
353353-(** [user_with_tool_result ~tool_use_id ~content ?is_error ()] creates a user message
354354- containing a tool result. *)
357357+val user_with_tool_result :
358358+ tool_use_id:string -> content:string -> ?is_error:bool -> unit -> t
359359+(** [user_with_tool_result ~tool_use_id ~content ?is_error ()] creates a user
360360+ message containing a tool result. *)
355361356356-val assistant : content:Content_block.t list -> model:string -> ?error:Assistant.error -> unit -> t
362362+val assistant :
363363+ content:Content_block.t list ->
364364+ model:string ->
365365+ ?error:Assistant.error ->
366366+ unit ->
367367+ t
357368(** [assistant ~content ~model ?error ()] creates an assistant message. *)
358369359359-val assistant_text : text:string -> model:string -> ?error:Assistant.error -> unit -> t
360360-(** [assistant_text ~text ~model ?error ()] creates an assistant message with only text content. *)
370370+val assistant_text :
371371+ text:string -> model:string -> ?error:Assistant.error -> unit -> t
372372+(** [assistant_text ~text ~model ?error ()] creates an assistant message with
373373+ only text content. *)
361374362375val system_init : session_id:string -> t
363376(** [system_init ~session_id] creates a system init message. *)
···376389 ?usage:Result.Usage.t ->
377390 ?result:string ->
378391 ?structured_output:Jsont.json ->
379379- unit -> t
392392+ unit ->
393393+ t
380394(** [result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
381381- ~session_id ?total_cost_usd ?usage ?result ()] creates a result message. *)
395395+ ~session_id ?total_cost_usd ?usage ?result ()] creates a result message. *)
382396383397val to_json : t -> Jsont.json
384398(** [to_json t] converts any message to its JSON representation. *)
···414428(** [extract_tool_uses t] extracts tool use blocks from assistant messages. *)
415429416430val get_session_id : t -> string option
417417-(** [get_session_id t] extracts the session ID from system or result messages. *)
431431+(** [get_session_id t] extracts the session ID from system or result messages.
432432+*)
418433419434(** {1 Logging} *)
420435···426441427442val log_error : string -> t -> unit
428443(** [log_error msg t] logs an error with the given message and context. *)
429429-
+3-4
lib/model.ml
···11-type t = [
22- | `Sonnet_4_5
11+type t =
22+ [ `Sonnet_4_5
33 | `Sonnet_4
44 | `Sonnet_3_5
55 | `Opus_4
66 | `Haiku_4
77- | `Custom of string
88-]
77+ | `Custom of string ]
98109let to_string = function
1110 | `Sonnet_4_5 -> "claude-sonnet-4-5"
+9-10
lib/model.mli
···44 model strings. Use polymorphic variants for known models with a custom
55 escape hatch for future or unknown models. *)
6677-type t = [
88- | `Sonnet_4_5 (** claude-sonnet-4-5 - Most recent Sonnet model *)
99- | `Sonnet_4 (** claude-sonnet-4 - Sonnet 4 model *)
1010- | `Sonnet_3_5 (** claude-sonnet-3-5 - Sonnet 3.5 model *)
1111- | `Opus_4 (** claude-opus-4 - Opus 4 model for complex tasks *)
1212- | `Haiku_4 (** claude-haiku-4 - Fast, cost-effective Haiku model *)
1313- | `Custom of string (** Custom model string for future/unknown models *)
1414-]
77+type t =
88+ [ `Sonnet_4_5 (** claude-sonnet-4-5 - Most recent Sonnet model *)
99+ | `Sonnet_4 (** claude-sonnet-4 - Sonnet 4 model *)
1010+ | `Sonnet_3_5 (** claude-sonnet-3-5 - Sonnet 3.5 model *)
1111+ | `Opus_4 (** claude-opus-4 - Opus 4 model for complex tasks *)
1212+ | `Haiku_4 (** claude-haiku-4 - Fast, cost-effective Haiku model *)
1313+ | `Custom of string (** Custom model string for future/unknown models *) ]
1514(** The type of Claude models. *)
16151716val to_string : t -> string
···2524val of_string : string -> t
2625(** [of_string s] parses a model string into a typed model.
27262828- Known model strings are converted to their typed variants.
2929- Unknown strings become [`Custom s].
2727+ Known model strings are converted to their typed variants. Unknown strings
2828+ become [`Custom s].
30293130 Examples:
3231 - "claude-sonnet-4-5" becomes [`Sonnet_4_5]
+126-112
lib/options.ml
···11let src = Logs.Src.create "claude.options" ~doc:"Claude configuration options"
22+23module Log = (val Logs.src_log src : Logs.LOG)
3445type setting_source = User | Project | Local
···3233 unknown : Unknown.t;
3334}
34353535-let default = {
3636- allowed_tools = [];
3737- disallowed_tools = [];
3838- max_thinking_tokens = 8000;
3939- system_prompt = None;
4040- append_system_prompt = None;
4141- permission_mode = None;
4242- permission_callback = Some Permissions.default_allow_callback;
4343- model = None;
4444- cwd = None;
4545- env = [];
4646- continue_conversation = false;
4747- resume = None;
4848- max_turns = None;
4949- permission_prompt_tool_name = None;
5050- settings = None;
5151- add_dirs = [];
5252- extra_args = [];
5353- debug_stderr = None;
5454- hooks = None;
5555- max_budget_usd = None;
5656- fallback_model = None;
5757- setting_sources = None;
5858- max_buffer_size = None;
5959- user = None;
6060- output_format = None;
6161- unknown = Unknown.empty;
6262-}
3636+let default =
3737+ {
3838+ allowed_tools = [];
3939+ disallowed_tools = [];
4040+ max_thinking_tokens = 8000;
4141+ system_prompt = None;
4242+ append_system_prompt = None;
4343+ permission_mode = None;
4444+ permission_callback = Some Permissions.default_allow_callback;
4545+ model = None;
4646+ cwd = None;
4747+ env = [];
4848+ continue_conversation = false;
4949+ resume = None;
5050+ max_turns = None;
5151+ permission_prompt_tool_name = None;
5252+ settings = None;
5353+ add_dirs = [];
5454+ extra_args = [];
5555+ debug_stderr = None;
5656+ hooks = None;
5757+ max_budget_usd = None;
5858+ fallback_model = None;
5959+ setting_sources = None;
6060+ max_buffer_size = None;
6161+ user = None;
6262+ output_format = None;
6363+ unknown = Unknown.empty;
6464+ }
63656464-let create
6565- ?(allowed_tools = [])
6666- ?(disallowed_tools = [])
6767- ?(max_thinking_tokens = 8000)
6868- ?system_prompt
6969- ?append_system_prompt
7070- ?permission_mode
7171- ?permission_callback
7272- ?model
7373- ?cwd
7474- ?(env = [])
7575- ?(continue_conversation = false)
7676- ?resume
7777- ?max_turns
7878- ?permission_prompt_tool_name
7979- ?settings
8080- ?(add_dirs = [])
8181- ?(extra_args = [])
8282- ?debug_stderr
8383- ?hooks
8484- ?max_budget_usd
8585- ?fallback_model
8686- ?setting_sources
8787- ?max_buffer_size
8888- ?user
8989- ?output_format
9090- ?(unknown = Unknown.empty)
9191- () =
9292- { allowed_tools; disallowed_tools; max_thinking_tokens;
9393- system_prompt; append_system_prompt; permission_mode;
9494- permission_callback; model; cwd; env;
9595- continue_conversation; resume; max_turns;
9696- permission_prompt_tool_name; settings; add_dirs;
9797- extra_args; debug_stderr; hooks;
9898- max_budget_usd; fallback_model; setting_sources;
9999- max_buffer_size; user; output_format; unknown }
6666+let create ?(allowed_tools = []) ?(disallowed_tools = [])
6767+ ?(max_thinking_tokens = 8000) ?system_prompt ?append_system_prompt
6868+ ?permission_mode ?permission_callback ?model ?cwd ?(env = [])
6969+ ?(continue_conversation = false) ?resume ?max_turns
7070+ ?permission_prompt_tool_name ?settings ?(add_dirs = []) ?(extra_args = [])
7171+ ?debug_stderr ?hooks ?max_budget_usd ?fallback_model ?setting_sources
7272+ ?max_buffer_size ?user ?output_format ?(unknown = Unknown.empty) () =
7373+ {
7474+ allowed_tools;
7575+ disallowed_tools;
7676+ max_thinking_tokens;
7777+ system_prompt;
7878+ append_system_prompt;
7979+ permission_mode;
8080+ permission_callback;
8181+ model;
8282+ cwd;
8383+ env;
8484+ continue_conversation;
8585+ resume;
8686+ max_turns;
8787+ permission_prompt_tool_name;
8888+ settings;
8989+ add_dirs;
9090+ extra_args;
9191+ debug_stderr;
9292+ hooks;
9393+ max_budget_usd;
9494+ fallback_model;
9595+ setting_sources;
9696+ max_buffer_size;
9797+ user;
9898+ output_format;
9999+ unknown;
100100+ }
100101101102let allowed_tools t = t.allowed_tools
102103let disallowed_tools t = t.disallowed_tools
···124125let user t = t.user
125126let output_format t = t.output_format
126127let unknown t = t.unknown
127127-128128let with_allowed_tools tools t = { t with allowed_tools = tools }
129129let with_disallowed_tools tools t = { t with disallowed_tools = tools }
130130let with_max_thinking_tokens tokens t = { t with max_thinking_tokens = tokens }
131131let with_system_prompt prompt t = { t with system_prompt = Some prompt }
132132-let with_append_system_prompt prompt t = { t with append_system_prompt = Some prompt }
132132+133133+let with_append_system_prompt prompt t =
134134+ { t with append_system_prompt = Some prompt }
135135+133136let with_permission_mode mode t = { t with permission_mode = Some mode }
134134-let with_permission_callback callback t = { t with permission_callback = Some callback }
137137+138138+let with_permission_callback callback t =
139139+ { t with permission_callback = Some callback }
140140+135141let with_model model t = { t with model = Some model }
136142let with_model_string model t = { t with model = Some (Model.of_string model) }
137143let with_cwd cwd t = { t with cwd = Some cwd }
138144let with_env env t = { t with env }
139139-let with_continue_conversation continue t = { t with continue_conversation = continue }
145145+146146+let with_continue_conversation continue t =
147147+ { t with continue_conversation = continue }
148148+140149let with_resume session_id t = { t with resume = Some session_id }
141150let with_max_turns turns t = { t with max_turns = Some turns }
142142-let with_permission_prompt_tool_name tool t = { t with permission_prompt_tool_name = Some tool }
151151+152152+let with_permission_prompt_tool_name tool t =
153153+ { t with permission_prompt_tool_name = Some tool }
154154+143155let with_settings path t = { t with settings = Some path }
144156let with_add_dirs dirs t = { t with add_dirs = dirs }
145157let with_extra_args args t = { t with extra_args = args }
···147159let with_hooks hooks t = { t with hooks = Some hooks }
148160let with_max_budget_usd budget t = { t with max_budget_usd = Some budget }
149161let with_fallback_model model t = { t with fallback_model = Some model }
150150-let with_fallback_model_string model t = { t with fallback_model = Some (Model.of_string model) }
162162+163163+let with_fallback_model_string model t =
164164+ { t with fallback_model = Some (Model.of_string model) }
165165+151166let with_setting_sources sources t = { t with setting_sources = Some sources }
152167let with_no_settings t = { t with setting_sources = Some [] }
153168let with_max_buffer_size size t = { t with max_buffer_size = Some size }
···156171157172(* Helper codec for Model.t *)
158173let model_jsont : Model.t Jsont.t =
159159- Jsont.map ~kind:"Model"
160160- ~dec:Model.of_string
161161- ~enc:Model.to_string
162162- Jsont.string
174174+ Jsont.map ~kind:"Model" ~dec:Model.of_string ~enc:Model.to_string Jsont.string
163175164176(* Helper codec for env - list of string pairs encoded as object.
165177 Env is a dynamic object where all values should be strings.
···169181 ~dec:(fun json ->
170182 match json with
171183 | Jsont.Object (members, _) ->
172172- List.filter_map (fun ((name, _), value) ->
173173- match Jsont.Json.decode Jsont.string value with
174174- | Ok s -> Some (name, s)
175175- | Error _ -> None
176176- ) members
184184+ List.filter_map
185185+ (fun ((name, _), value) ->
186186+ match Jsont.Json.decode Jsont.string value with
187187+ | Ok s -> Some (name, s)
188188+ | Error _ -> None)
189189+ members
177190 | _ -> [])
178191 ~enc:(fun pairs ->
179179- Jsont.Json.object' (List.map (fun (k, v) ->
180180- Jsont.Json.mem (Jsont.Json.name k) (Jsont.Json.string v)
181181- ) pairs))
192192+ Jsont.Json.object'
193193+ (List.map
194194+ (fun (k, v) ->
195195+ Jsont.Json.mem (Jsont.Json.name k) (Jsont.Json.string v))
196196+ pairs))
182197 Jsont.json
183198184199let jsont : t Jsont.t =
185185- let make allowed_tools disallowed_tools max_thinking_tokens
186186- system_prompt append_system_prompt permission_mode
187187- model env unknown =
188188- { allowed_tools; disallowed_tools; max_thinking_tokens;
189189- system_prompt; append_system_prompt; permission_mode;
200200+ let make allowed_tools disallowed_tools max_thinking_tokens system_prompt
201201+ append_system_prompt permission_mode model env unknown =
202202+ {
203203+ allowed_tools;
204204+ disallowed_tools;
205205+ max_thinking_tokens;
206206+ system_prompt;
207207+ append_system_prompt;
208208+ permission_mode;
190209 permission_callback = Some Permissions.default_allow_callback;
191191- model; cwd = None; env;
210210+ model;
211211+ cwd = None;
212212+ env;
192213 continue_conversation = false;
193214 resume = None;
194215 max_turns = None;
···204225 max_buffer_size = None;
205226 user = None;
206227 output_format = None;
207207- unknown }
228228+ unknown;
229229+ }
208230 in
209209- Jsont.Object.map ~kind:"Options" make
210210- |> Jsont.Object.mem "allowed_tools" (Jsont.list Jsont.string) ~enc:allowed_tools ~dec_absent:[]
211211- |> Jsont.Object.mem "disallowed_tools" (Jsont.list Jsont.string) ~enc:disallowed_tools ~dec_absent:[]
212212- |> Jsont.Object.mem "max_thinking_tokens" Jsont.int ~enc:max_thinking_tokens ~dec_absent:8000
213213- |> Jsont.Object.opt_mem "system_prompt" Jsont.string ~enc:system_prompt
214214- |> Jsont.Object.opt_mem "append_system_prompt" Jsont.string ~enc:append_system_prompt
215215- |> Jsont.Object.opt_mem "permission_mode" Permissions.Mode.jsont ~enc:permission_mode
216216- |> Jsont.Object.opt_mem "model" model_jsont ~enc:model
217217- |> Jsont.Object.mem "env" env_jsont ~enc:env ~dec_absent:[]
218218- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
219219- |> Jsont.Object.finish
220220-221221-(*
222222-let to_json t =
223223- match Jsont.Json.encode jsont t with
224224- | Ok json -> json
225225- | Error msg -> failwith ("Options.to_json: " ^ msg)
226226-227227-let of_json json =
228228- match Jsont.Json.decode jsont json with
229229- | Ok t -> t
230230- | Error msg -> raise (Invalid_argument ("Options.of_json: " ^ msg))
231231-*)
231231+ Jsont.Object.(
232232+ map ~kind:"Options" make
233233+ |> mem "allowed_tools" (Jsont.list Jsont.string) ~enc:allowed_tools
234234+ ~dec_absent:[]
235235+ |> mem "disallowed_tools" (Jsont.list Jsont.string) ~enc:disallowed_tools
236236+ ~dec_absent:[]
237237+ |> mem "max_thinking_tokens" Jsont.int ~enc:max_thinking_tokens
238238+ ~dec_absent:8000
239239+ |> opt_mem "system_prompt" Jsont.string ~enc:system_prompt
240240+ |> opt_mem "append_system_prompt" Jsont.string ~enc:append_system_prompt
241241+ |> opt_mem "permission_mode" Permissions.Mode.jsont ~enc:permission_mode
242242+ |> opt_mem "model" model_jsont ~enc:model
243243+ |> mem "env" env_jsont ~enc:env ~dec_absent:[]
244244+ |> keep_unknown Jsont.json_mems ~enc:unknown
245245+ |> finish)
232246233247let log_options t =
234248 Log.debug (fun m -> m "Claude options: %a" (Jsont.pp_value jsont ()) t)
+73-51
lib/options.mli
···17171818 {2 Builder Pattern}
19192020- Options use a functional builder pattern - each [with_*] function returns
2121- a new options value with the specified field updated:
2020+ Options use a functional builder pattern - each [with_*] function returns a
2121+ new options value with the specified field updated:
22222323 {[
2424- let options = Options.default
2424+ let options =
2525+ Options.default
2526 |> Options.with_model "claude-sonnet-4-5"
2627 |> Options.with_max_budget_usd 1.0
2728 |> Options.with_permission_mode Permissions.Mode.Accept_edits
···3233 {3 CI/CD: Isolated, Reproducible Builds}
33343435 {[
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
3636+ let ci_config =
3737+ Options.default |> Options.with_no_settings (* Ignore user config *)
3838+ |> Options.with_max_budget_usd 0.50 (* 50 cent limit *)
3939+ |> Options.with_permission_mode Permissions.Mode.Bypass_permissions
4040 |> Options.with_model "claude-haiku-4"
4141 ]}
42424343 {3 Production: Cost Control with Fallback}
44444545 {[
4646- let prod_config = Options.default
4646+ let prod_config =
4747+ Options.default
4748 |> Options.with_model "claude-sonnet-4-5"
4849 |> Options.with_fallback_model "claude-haiku-4"
4949- |> Options.with_max_budget_usd 10.0 (* $10 daily limit *)
5050+ |> Options.with_max_budget_usd 10.0 (* $10 daily limit *)
5051 |> Options.with_max_buffer_size 5_000_000
5152 ]}
52535354 {3 Development: User Settings with Overrides}
54555556 {[
5656- let dev_config = Options.default
5757- |> Options.with_setting_sources [User; Project]
5757+ let dev_config =
5858+ Options.default
5959+ |> Options.with_setting_sources [ User; Project ]
5860 |> Options.with_max_budget_usd 1.0
5961 |> Options.with_permission_mode Permissions.Mode.Default
6062 ]}
···6264 {3 Structured Output: Type-Safe Responses}
63656466 {[
6565- let schema = Jsont.json_of_json (`O [
6666- ("type", `String "object");
6767- ("properties", `O [
6868- ("count", `O [("type", `String "integer")]);
6969- ("has_tests", `O [("type", `String "boolean")]);
7070- ]);
7171- ])
6767+ let schema =
6868+ Jsont.json_of_json
6969+ (`O
7070+ [
7171+ ("type", `String "object");
7272+ ( "properties",
7373+ `O
7474+ [
7575+ ("count", `O [ ("type", `String "integer") ]);
7676+ ("has_tests", `O [ ("type", `String "boolean") ]);
7777+ ] );
7878+ ])
7979+7280 let format = Structured_output.of_json_schema schema
73817474- let analysis_config = Options.default
8282+ let analysis_config =
8383+ Options.default
7584 |> Options.with_output_format format
7676- |> Options.with_allowed_tools ["Read"; "Glob"; "Grep"]
8585+ |> Options.with_allowed_tools [ "Read"; "Glob"; "Grep" ]
7786 ]}
78877988 {2 Advanced Options}
···9099 - [User] - ~/.claude/config
91100 - [Project] - .claude/ in project root
92101 - [Local] - Current directory settings
9393- - [Some \[\]] (via {!with_no_settings}) - No settings, fully isolated
102102+ - [Some []] (via {!with_no_settings}) - No settings, fully isolated
9410395104 This is critical for reproducible builds in CI/CD environments.
9610597106 {3 Model Fallback}
981079999- Use {!with_fallback_model} to specify an alternative model when the
100100- primary model is unavailable or overloaded. This improves reliability. *)
108108+ Use {!with_fallback_model} to specify an alternative model when the primary
109109+ model is unavailable or overloaded. This improves reliability. *)
101110102102-(** The log source for options operations *)
103111val src : Logs.Src.t
112112+(** The log source for options operations *)
104113105114(** {1 Types} *)
106115107107-type setting_source = User | Project | Local
108108-(** Setting source determines which configuration files to load.
109109- - [User]: Load user-level settings from ~/.claude/config
110110- - [Project]: Load project-level settings from .claude/ in project root
111111- - [Local]: Load local settings from current directory *)
116116+type setting_source =
117117+ | User
118118+ | Project
119119+ | Local
120120+ (** Setting source determines which configuration files to load.
121121+ - [User]: Load user-level settings from ~/.claude/config
122122+ - [Project]: Load project-level settings from .claude/ in project root
123123+ - [Local]: Load local settings from current directory *)
112124113125type t
114126(** The type of configuration options. *)
···147159 ?user:string ->
148160 ?output_format:Structured_output.t ->
149161 ?unknown:Jsont.json ->
150150- unit -> t
162162+ unit ->
163163+ t
151164(** [create ?allowed_tools ?disallowed_tools ?max_thinking_tokens ?system_prompt
152152- ?append_system_prompt ?permission_mode ?permission_callback ?model ?cwd ?env
153153- ?continue_conversation ?resume ?max_turns ?permission_prompt_tool_name ?settings
154154- ?add_dirs ?extra_args ?debug_stderr ?hooks ?max_budget_usd ?fallback_model
155155- ?setting_sources ?max_buffer_size ?user ()]
156156- creates a new configuration.
165165+ ?append_system_prompt ?permission_mode ?permission_callback ?model ?cwd
166166+ ?env ?continue_conversation ?resume ?max_turns ?permission_prompt_tool_name
167167+ ?settings ?add_dirs ?extra_args ?debug_stderr ?hooks ?max_budget_usd
168168+ ?fallback_model ?setting_sources ?max_buffer_size ?user ()] creates a new
169169+ configuration.
157170 @param allowed_tools List of explicitly allowed tool names
158171 @param disallowed_tools List of explicitly disallowed tool names
159159- @param max_thinking_tokens Maximum tokens for thinking blocks (default: 8000)
172172+ @param max_thinking_tokens
173173+ Maximum tokens for thinking blocks (default: 8000)
160174 @param system_prompt Replace the default system prompt
161175 @param append_system_prompt Append to the default system prompt
162176 @param permission_mode Permission mode to use
···213227(** [env t] returns the environment variables. *)
214228215229val continue_conversation : t -> bool
216216-(** [continue_conversation t] returns whether to continue an existing conversation. *)
230230+(** [continue_conversation t] returns whether to continue an existing
231231+ conversation. *)
217232218233val resume : t -> string option
219234(** [resume t] returns the optional session ID to resume. *)
···222237(** [max_turns t] returns the optional maximum number of turns. *)
223238224239val permission_prompt_tool_name : t -> string option
225225-(** [permission_prompt_tool_name t] returns the optional tool name for permission prompts. *)
240240+(** [permission_prompt_tool_name t] returns the optional tool name for
241241+ permission prompts. *)
226242227243val settings : t -> string option
228244(** [settings t] returns the optional path to settings file. *)
···258274(** [output_format t] returns the optional structured output format. *)
259275260276val unknown : t -> Jsont.json
261261-(** [unknown t] returns any unknown JSON fields that were preserved during decoding. *)
277277+(** [unknown t] returns any unknown JSON fields that were preserved during
278278+ decoding. *)
262279263280(** {1 Builders} *)
264281···287304(** [with_model model t] sets the model override using a typed Model.t. *)
288305289306val with_model_string : string -> t -> t
290290-(** [with_model_string model t] sets the model override from a string.
291291- The string is parsed using {!Model.of_string}. *)
307307+(** [with_model_string model t] sets the model override from a string. The
308308+ string is parsed using {!Model.of_string}. *)
292309293310val with_cwd : Eio.Fs.dir_ty Eio.Path.t -> t -> t
294311(** [with_cwd cwd t] sets the working directory. *)
···297314(** [with_env env t] sets the environment variables. *)
298315299316val with_continue_conversation : bool -> t -> t
300300-(** [with_continue_conversation continue t] sets whether to continue conversation. *)
317317+(** [with_continue_conversation continue t] sets whether to continue
318318+ conversation. *)
301319302320val with_resume : string -> t -> t
303321(** [with_resume session_id t] sets the session ID to resume. *)
···306324(** [with_max_turns turns t] sets the maximum number of turns. *)
307325308326val with_permission_prompt_tool_name : string -> t -> t
309309-(** [with_permission_prompt_tool_name tool t] sets the permission prompt tool name. *)
327327+(** [with_permission_prompt_tool_name tool t] sets the permission prompt tool
328328+ name. *)
310329311330val with_settings : string -> t -> t
312331(** [with_settings path t] sets the path to settings file. *)
···324343(** [with_hooks hooks t] sets the hooks configuration. *)
325344326345val with_max_budget_usd : float -> t -> t
327327-(** [with_max_budget_usd budget t] sets the maximum spending limit in USD.
328328- The session will terminate if this limit is exceeded. *)
346346+(** [with_max_budget_usd budget t] sets the maximum spending limit in USD. The
347347+ session will terminate if this limit is exceeded. *)
329348330349val with_fallback_model : Model.t -> t -> t
331331-(** [with_fallback_model model t] sets the fallback model using a typed Model.t. *)
350350+(** [with_fallback_model model t] sets the fallback model using a typed Model.t.
351351+*)
332352333353val with_fallback_model_string : string -> t -> t
334354(** [with_fallback_model_string model t] sets the fallback model from a string.
···340360341361val with_no_settings : t -> t
342362(** [with_no_settings t] disables all settings loading (user, project, local).
343343- Useful for CI/CD environments where you want isolated, reproducible behavior. *)
363363+ Useful for CI/CD environments where you want isolated, reproducible
364364+ behavior. *)
344365345366val with_max_buffer_size : int -> t -> t
346346-(** [with_max_buffer_size size t] sets the maximum stdout buffer size in bytes. *)
367367+(** [with_max_buffer_size size t] sets the maximum stdout buffer size in bytes.
368368+*)
347369348370val with_user : string -> t -> t
349371(** [with_user user t] sets the Unix user for subprocess execution. *)
···354376(** {1 Serialization} *)
355377356378val jsont : t Jsont.t
357357-(** [jsont] is the Jsont codec for Options.t
358358- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
379379+(** [jsont] is the Jsont codec for Options.t Use [Jsont.pp_value jsont ()] for
380380+ pretty-printing. *)
359381360382(** {1 Logging} *)
361383
···11(** Permission system for Claude tool invocations.
2233- This module provides a permission system for controlling
44- which tools Claude can invoke and how they can be used. It includes
55- support for permission modes, rules, updates, and callbacks. *)
33+ This module provides a permission system for controlling which tools Claude
44+ can invoke and how they can be used. It includes support for permission
55+ modes, rules, updates, and callbacks. *)
6677-(** The log source for permission operations *)
87val src : Logs.Src.t
88+(** The log source for permission operations *)
991010(** {1 Permission Modes} *)
11111212module Mode : sig
1313 (** Permission modes control the overall behavior of the permission system. *)
14141515+ (** The type of permission modes. *)
1516 type t =
1617 | Default (** Standard permission mode with normal checks *)
1718 | Accept_edits (** Automatically accept file edits *)
1819 | Plan (** Planning mode with restricted execution *)
1920 | Bypass_permissions (** Bypass all permission checks *)
2020- (** The type of permission modes. *)
21212222 val to_string : t -> string
2323 (** [to_string t] converts a mode to its string representation. *)
···2727 @raise Invalid_argument if the string is not a valid mode. *)
28282929 val jsont : t Jsont.t
3030- (** [jsont] is the Jsont codec for permission modes.
3131- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
3030+ (** [jsont] is the Jsont codec for permission modes. Use
3131+ [Jsont.pp_value jsont ()] for pretty-printing. *)
3232end
33333434(** {1 Permission Behaviors} *)
···3636module Behavior : sig
3737 (** Behaviors determine how permission requests are handled. *)
38383939+ (** The type of permission behaviors. *)
3940 type t =
4041 | Allow (** Allow the operation *)
4142 | Deny (** Deny the operation *)
4243 | Ask (** Ask the user for permission *)
4343- (** The type of permission behaviors. *)
44444545 val to_string : t -> string
4646 (** [to_string t] converts a behavior to its string representation. *)
···5050 @raise Invalid_argument if the string is not a valid behavior. *)
51515252 val jsont : t Jsont.t
5353- (** [jsont] is the Jsont codec for permission behaviors.
5454- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
5353+ (** [jsont] is the Jsont codec for permission behaviors. Use
5454+ [Jsont.pp_value jsont ()] for pretty-printing. *)
5555end
56565757(** {1 Permission Rules} *)
···6666 }
6767 (** The type of permission rules. *)
68686969- val create : tool_name:string -> ?rule_content:string -> ?unknown:Unknown.t -> unit -> t
6969+ val create :
7070+ tool_name:string -> ?rule_content:string -> ?unknown:Unknown.t -> unit -> t
7071 (** [create ~tool_name ?rule_content ?unknown ()] creates a new rule.
7172 @param tool_name The name of the tool this rule applies to
7273 @param rule_content Optional rule specification or pattern
···8283 (** [unknown t] returns the unknown fields. *)
83848485 val jsont : t Jsont.t
8585- (** [jsont] is the Jsont codec for permission rules.
8686- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
8686+ (** [jsont] is the Jsont codec for permission rules. Use
8787+ [Jsont.pp_value jsont ()] for pretty-printing. *)
8788end
88898990(** {1 Permission Updates} *)
···9192module Update : sig
9293 (** Updates modify permission settings. *)
93949595+ (** The destination for permission updates. *)
9496 type destination =
9597 | User_settings (** Apply to user settings *)
9698 | Project_settings (** Apply to project settings *)
9799 | Local_settings (** Apply to local settings *)
98100 | Session (** Apply to current session only *)
9999- (** The destination for permission updates. *)
100101102102+ (** The type of permission update. *)
101103 type update_type =
102104 | Add_rules (** Add new rules *)
103105 | Replace_rules (** Replace existing rules *)
···105107 | Set_mode (** Set permission mode *)
106108 | Add_directories (** Add allowed directories *)
107109 | Remove_directories (** Remove allowed directories *)
108108- (** The type of permission update. *)
109110110111 type t
111112 (** The type of permission updates. *)
···118119 ?directories:string list ->
119120 ?destination:destination ->
120121 ?unknown:Unknown.t ->
121121- unit -> t
122122- (** [create ~update_type ?rules ?behavior ?mode ?directories ?destination ?unknown ()]
123123- creates a new permission update.
122122+ unit ->
123123+ t
124124+ (** [create ~update_type ?rules ?behavior ?mode ?directories ?destination
125125+ ?unknown ()] creates a new permission update.
124126 @param update_type The type of update to perform
125127 @param rules Optional list of rules to add/remove/replace
126128 @param behavior Optional behavior to set
···151153 (** [unknown t] returns the unknown fields. *)
152154153155 val jsont : t Jsont.t
154154- (** [jsont] is the Jsont codec for permission updates.
155155- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
156156+ (** [jsont] is the Jsont codec for permission updates. Use
157157+ [Jsont.pp_value jsont ()] for pretty-printing. *)
156158end
157159158160(** {1 Permission Context} *)
···178180 (** [unknown t] returns the unknown fields. *)
179181180182 val jsont : t Jsont.t
181181- (** [jsont] is the Jsont codec for permission context.
182182- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
183183+ (** [jsont] is the Jsont codec for permission context. Use
184184+ [Jsont.pp_value jsont ()] for pretty-printing. *)
183185end
184186185187(** {1 Permission Results} *)
···190192 type t =
191193 | Allow of {
192194 updated_input : Jsont.json option; (** Modified tool input *)
193193- updated_permissions : Update.t list option; (** Permission updates to apply *)
195195+ updated_permissions : Update.t list option;
196196+ (** Permission updates to apply *)
194197 unknown : Unknown.t; (** Unknown fields *)
195198 }
196199 | Deny of {
197200 message : string; (** Reason for denial *)
198201 interrupt : bool; (** Whether to interrupt execution *)
199202 unknown : Unknown.t; (** Unknown fields *)
200200- }
201201- (** The type of permission results. *)
203203+ } (** The type of permission results. *)
202204203203- val allow : ?updated_input:Jsont.json -> ?updated_permissions:Update.t list -> ?unknown:Unknown.t -> unit -> t
204204- (** [allow ?updated_input ?updated_permissions ?unknown ()] creates an allow result.
205205+ val allow :
206206+ ?updated_input:Jsont.json ->
207207+ ?updated_permissions:Update.t list ->
208208+ ?unknown:Unknown.t ->
209209+ unit ->
210210+ t
211211+ (** [allow ?updated_input ?updated_permissions ?unknown ()] creates an allow
212212+ result.
205213 @param updated_input Optional modified tool input
206214 @param updated_permissions Optional permission updates to apply
207215 @param unknown Optional unknown fields to preserve *)
···213221 @param unknown Optional unknown fields to preserve *)
214222215223 val jsont : t Jsont.t
216216- (** [jsont] is the Jsont codec for permission results.
217217- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
224224+ (** [jsont] is the Jsont codec for permission results. Use
225225+ [Jsont.pp_value jsont ()] for pretty-printing. *)
218226end
219227220228(** {1 Permission Callbacks} *)
221229222230type callback =
223223- tool_name:string ->
224224- input:Jsont.json ->
225225- context:Context.t ->
226226- Result.t
227227-(** The type of permission callbacks. Callbacks are invoked when Claude
228228- attempts to use a tool, allowing custom permission logic. *)
231231+ tool_name:string -> input:Jsont.json -> context:Context.t -> Result.t
232232+(** The type of permission callbacks. Callbacks are invoked when Claude attempts
233233+ to use a tool, allowing custom permission logic. *)
229234230235val default_allow_callback : callback
231236(** [default_allow_callback] always allows tool invocations. *)
232237233238val discovery_callback : Rule.t list ref -> callback
234234-(** [discovery_callback log] creates a callback that collects suggested
235235- rules into the provided reference. Useful for discovering what
236236- permissions an operation requires. *)
239239+(** [discovery_callback log] creates a callback that collects suggested rules
240240+ into the provided reference. Useful for discovering what permissions an
241241+ operation requires. *)
237242238243(** {1 Logging} *)
239244240245val log_permission_check : tool_name:string -> result:Result.t -> unit
241241-(** [log_permission_check ~tool_name ~result] logs a permission check result. *)246246+(** [log_permission_check ~tool_name ~result] logs a permission check result. *)
+216-157
lib/sdk_control.ml
···11-let src = Logs.Src.create "claude.sdk_control" ~doc:"Claude SDK control protocol"
11+let src =
22+ Logs.Src.create "claude.sdk_control" ~doc:"Claude SDK control protocol"
33+24module Log = (val Logs.src_log src : Logs.LOG)
3546module Request = struct
55- type interrupt = {
66- subtype : [`Interrupt];
77- unknown : Unknown.t;
88- }
77+ type interrupt = { subtype : [ `Interrupt ]; unknown : Unknown.t }
98109 type permission = {
1111- subtype : [`Can_use_tool];
1010+ subtype : [ `Can_use_tool ];
1211 tool_name : string;
1312 input : Jsont.json;
1413 permission_suggestions : Permissions.Update.t list option;
···1716 }
18171918 type initialize = {
2020- subtype : [`Initialize];
1919+ subtype : [ `Initialize ];
2120 hooks : (string * Jsont.json) list option;
2221 unknown : Unknown.t;
2322 }
24232524 type set_permission_mode = {
2626- subtype : [`Set_permission_mode];
2525+ subtype : [ `Set_permission_mode ];
2726 mode : Permissions.Mode.t;
2827 unknown : Unknown.t;
2928 }
30293130 type hook_callback = {
3232- subtype : [`Hook_callback];
3131+ subtype : [ `Hook_callback ];
3332 callback_id : string;
3433 input : Jsont.json;
3534 tool_use_id : string option;
···3736 }
38373938 type mcp_message = {
4040- subtype : [`Mcp_message];
3939+ subtype : [ `Mcp_message ];
4140 server_name : string;
4241 message : Jsont.json;
4342 unknown : Unknown.t;
4443 }
45444645 type set_model = {
4747- subtype : [`Set_model];
4646+ subtype : [ `Set_model ];
4847 model : string;
4948 unknown : Unknown.t;
5049 }
51505252- type get_server_info = {
5353- subtype : [`Get_server_info];
5454- unknown : Unknown.t;
5555- }
5151+ type get_server_info = { subtype : [ `Get_server_info ]; unknown : Unknown.t }
56525753 type t =
5854 | Interrupt of interrupt
···6359 | Mcp_message of mcp_message
6460 | Set_model of set_model
6561 | Get_server_info of get_server_info
6666-6262+6763 let interrupt ?(unknown = Unknown.empty) () =
6864 Interrupt { subtype = `Interrupt; unknown }
69657070- let permission ~tool_name ~input ?permission_suggestions ?blocked_path ?(unknown = Unknown.empty) () =
7171- Permission {
7272- subtype = `Can_use_tool;
7373- tool_name;
7474- input;
7575- permission_suggestions;
7676- blocked_path;
7777- unknown;
7878- }
6666+ let permission ~tool_name ~input ?permission_suggestions ?blocked_path
6767+ ?(unknown = Unknown.empty) () =
6868+ Permission
6969+ {
7070+ subtype = `Can_use_tool;
7171+ tool_name;
7272+ input;
7373+ permission_suggestions;
7474+ blocked_path;
7575+ unknown;
7676+ }
79778078 let initialize ?hooks ?(unknown = Unknown.empty) () =
8179 Initialize { subtype = `Initialize; hooks; unknown }
···8381 let set_permission_mode ~mode ?(unknown = Unknown.empty) () =
8482 Set_permission_mode { subtype = `Set_permission_mode; mode; unknown }
85838686- let hook_callback ~callback_id ~input ?tool_use_id ?(unknown = Unknown.empty) () =
8787- Hook_callback {
8888- subtype = `Hook_callback;
8989- callback_id;
9090- input;
9191- tool_use_id;
9292- unknown;
9393- }
8484+ let hook_callback ~callback_id ~input ?tool_use_id ?(unknown = Unknown.empty)
8585+ () =
8686+ Hook_callback
8787+ { subtype = `Hook_callback; callback_id; input; tool_use_id; unknown }
94889589 let mcp_message ~server_name ~message ?(unknown = Unknown.empty) () =
9696- Mcp_message {
9797- subtype = `Mcp_message;
9898- server_name;
9999- message;
100100- unknown;
101101- }
9090+ Mcp_message { subtype = `Mcp_message; server_name; message; unknown }
1029110392 let set_model ~model ?(unknown = Unknown.empty) () =
10493 Set_model { subtype = `Set_model; model; unknown }
···1089710998 (* Individual record codecs *)
11099 let interrupt_jsont : interrupt Jsont.t =
111111- let make (unknown : Unknown.t) : interrupt = { subtype = `Interrupt; unknown } in
100100+ let make (unknown : Unknown.t) : interrupt =
101101+ { subtype = `Interrupt; unknown }
102102+ in
112103 Jsont.Object.map ~kind:"Interrupt" make
113113- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : interrupt) -> r.unknown)
104104+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : interrupt) ->
105105+ r.unknown)
114106 |> Jsont.Object.finish
115107116108 let permission_jsont : permission Jsont.t =
117117- let make tool_name input permission_suggestions blocked_path (unknown : Unknown.t) : permission =
118118- { subtype = `Can_use_tool; tool_name; input; permission_suggestions; blocked_path; unknown }
109109+ let make tool_name input permission_suggestions blocked_path
110110+ (unknown : Unknown.t) : permission =
111111+ {
112112+ subtype = `Can_use_tool;
113113+ tool_name;
114114+ input;
115115+ permission_suggestions;
116116+ blocked_path;
117117+ unknown;
118118+ }
119119 in
120120 Jsont.Object.map ~kind:"Permission" make
121121- |> Jsont.Object.mem "tool_name" Jsont.string ~enc:(fun (r : permission) -> r.tool_name)
122122- |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : permission) -> r.input)
123123- |> Jsont.Object.opt_mem "permission_suggestions" (Jsont.list Permissions.Update.jsont) ~enc:(fun (r : permission) -> r.permission_suggestions)
124124- |> Jsont.Object.opt_mem "blocked_path" Jsont.string ~enc:(fun (r : permission) -> r.blocked_path)
125125- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : permission) -> r.unknown)
121121+ |> Jsont.Object.mem "tool_name" Jsont.string ~enc:(fun (r : permission) ->
122122+ r.tool_name)
123123+ |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : permission) ->
124124+ r.input)
125125+ |> Jsont.Object.opt_mem "permission_suggestions"
126126+ (Jsont.list Permissions.Update.jsont) ~enc:(fun (r : permission) ->
127127+ r.permission_suggestions)
128128+ |> Jsont.Object.opt_mem "blocked_path" Jsont.string
129129+ ~enc:(fun (r : permission) -> r.blocked_path)
130130+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : permission) ->
131131+ r.unknown)
126132 |> Jsont.Object.finish
127133128134 let initialize_jsont : initialize Jsont.t =
129135 (* The hooks field is an object with string keys and json values *)
130136 let hooks_map_jsont = Jsont.Object.as_string_map Jsont.json in
131131- let module StringMap = Map.Make(String) in
132132- let hooks_jsont = Jsont.map
133133- ~dec:(fun m -> StringMap.bindings m)
134134- ~enc:(fun l -> StringMap.of_seq (List.to_seq l))
135135- hooks_map_jsont
137137+ let module StringMap = Map.Make (String) in
138138+ let hooks_jsont =
139139+ Jsont.map
140140+ ~dec:(fun m -> StringMap.bindings m)
141141+ ~enc:(fun l -> StringMap.of_seq (List.to_seq l))
142142+ hooks_map_jsont
136143 in
137137- let make hooks (unknown : Unknown.t) : initialize = { subtype = `Initialize; hooks; unknown } in
144144+ let make hooks (unknown : Unknown.t) : initialize =
145145+ { subtype = `Initialize; hooks; unknown }
146146+ in
138147 Jsont.Object.map ~kind:"Initialize" make
139139- |> Jsont.Object.opt_mem "hooks" hooks_jsont ~enc:(fun (r : initialize) -> r.hooks)
140140- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : initialize) -> r.unknown)
148148+ |> Jsont.Object.opt_mem "hooks" hooks_jsont ~enc:(fun (r : initialize) ->
149149+ r.hooks)
150150+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : initialize) ->
151151+ r.unknown)
141152 |> Jsont.Object.finish
142153143154 let set_permission_mode_jsont : set_permission_mode Jsont.t =
144144- let make mode (unknown : Unknown.t) : set_permission_mode = { subtype = `Set_permission_mode; mode; unknown } in
155155+ let make mode (unknown : Unknown.t) : set_permission_mode =
156156+ { subtype = `Set_permission_mode; mode; unknown }
157157+ in
145158 Jsont.Object.map ~kind:"SetPermissionMode" make
146146- |> Jsont.Object.mem "mode" Permissions.Mode.jsont ~enc:(fun (r : set_permission_mode) -> r.mode)
147147- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : set_permission_mode) -> r.unknown)
159159+ |> Jsont.Object.mem "mode" Permissions.Mode.jsont
160160+ ~enc:(fun (r : set_permission_mode) -> r.mode)
161161+ |> Jsont.Object.keep_unknown Jsont.json_mems
162162+ ~enc:(fun (r : set_permission_mode) -> r.unknown)
148163 |> Jsont.Object.finish
149164150165 let hook_callback_jsont : hook_callback Jsont.t =
151151- let make callback_id input tool_use_id (unknown : Unknown.t) : hook_callback =
166166+ let make callback_id input tool_use_id (unknown : Unknown.t) : hook_callback
167167+ =
152168 { subtype = `Hook_callback; callback_id; input; tool_use_id; unknown }
153169 in
154170 Jsont.Object.map ~kind:"HookCallback" make
155155- |> Jsont.Object.mem "callback_id" Jsont.string ~enc:(fun (r : hook_callback) -> r.callback_id)
156156- |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : hook_callback) -> r.input)
157157- |> Jsont.Object.opt_mem "tool_use_id" Jsont.string ~enc:(fun (r : hook_callback) -> r.tool_use_id)
158158- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : hook_callback) -> r.unknown)
171171+ |> Jsont.Object.mem "callback_id" Jsont.string
172172+ ~enc:(fun (r : hook_callback) -> r.callback_id)
173173+ |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : hook_callback) ->
174174+ r.input)
175175+ |> Jsont.Object.opt_mem "tool_use_id" Jsont.string
176176+ ~enc:(fun (r : hook_callback) -> r.tool_use_id)
177177+ |> Jsont.Object.keep_unknown Jsont.json_mems
178178+ ~enc:(fun (r : hook_callback) -> r.unknown)
159179 |> Jsont.Object.finish
160180161181 let mcp_message_jsont : mcp_message Jsont.t =
···163183 { subtype = `Mcp_message; server_name; message; unknown }
164184 in
165185 Jsont.Object.map ~kind:"McpMessage" make
166166- |> Jsont.Object.mem "server_name" Jsont.string ~enc:(fun (r : mcp_message) -> r.server_name)
167167- |> Jsont.Object.mem "message" Jsont.json ~enc:(fun (r : mcp_message) -> r.message)
168168- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : mcp_message) -> r.unknown)
186186+ |> Jsont.Object.mem "server_name" Jsont.string
187187+ ~enc:(fun (r : mcp_message) -> r.server_name)
188188+ |> Jsont.Object.mem "message" Jsont.json ~enc:(fun (r : mcp_message) ->
189189+ r.message)
190190+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : mcp_message) ->
191191+ r.unknown)
169192 |> Jsont.Object.finish
170193171194 let set_model_jsont : set_model Jsont.t =
172172- let make model (unknown : Unknown.t) : set_model = { subtype = `Set_model; model; unknown } in
195195+ let make model (unknown : Unknown.t) : set_model =
196196+ { subtype = `Set_model; model; unknown }
197197+ in
173198 Jsont.Object.map ~kind:"SetModel" make
174174- |> Jsont.Object.mem "model" Jsont.string ~enc:(fun (r : set_model) -> r.model)
175175- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : set_model) -> r.unknown)
199199+ |> Jsont.Object.mem "model" Jsont.string ~enc:(fun (r : set_model) ->
200200+ r.model)
201201+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : set_model) ->
202202+ r.unknown)
176203 |> Jsont.Object.finish
177204178205 let get_server_info_jsont : get_server_info Jsont.t =
179179- let make (unknown : Unknown.t) : get_server_info = { subtype = `Get_server_info; unknown } in
206206+ let make (unknown : Unknown.t) : get_server_info =
207207+ { subtype = `Get_server_info; unknown }
208208+ in
180209 Jsont.Object.map ~kind:"GetServerInfo" make
181181- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : get_server_info) -> r.unknown)
210210+ |> Jsont.Object.keep_unknown Jsont.json_mems
211211+ ~enc:(fun (r : get_server_info) -> r.unknown)
182212 |> Jsont.Object.finish
183213184214 (* Main variant codec using subtype discriminator *)
185215 let jsont : t Jsont.t =
186186- let case_interrupt = Jsont.Object.Case.map "interrupt" interrupt_jsont ~dec:(fun v -> Interrupt v) in
187187- let case_permission = Jsont.Object.Case.map "can_use_tool" permission_jsont ~dec:(fun v -> Permission v) in
188188- let case_initialize = Jsont.Object.Case.map "initialize" initialize_jsont ~dec:(fun v -> Initialize v) in
189189- let case_set_permission_mode = Jsont.Object.Case.map "set_permission_mode" set_permission_mode_jsont ~dec:(fun v -> Set_permission_mode v) in
190190- let case_hook_callback = Jsont.Object.Case.map "hook_callback" hook_callback_jsont ~dec:(fun v -> Hook_callback v) in
191191- let case_mcp_message = Jsont.Object.Case.map "mcp_message" mcp_message_jsont ~dec:(fun v -> Mcp_message v) in
192192- let case_set_model = Jsont.Object.Case.map "set_model" set_model_jsont ~dec:(fun v -> Set_model v) in
193193- let case_get_server_info = Jsont.Object.Case.map "get_server_info" get_server_info_jsont ~dec:(fun v -> Get_server_info v) in
216216+ let case_interrupt =
217217+ Jsont.Object.Case.map "interrupt" interrupt_jsont ~dec:(fun v ->
218218+ Interrupt v)
219219+ in
220220+ let case_permission =
221221+ Jsont.Object.Case.map "can_use_tool" permission_jsont ~dec:(fun v ->
222222+ Permission v)
223223+ in
224224+ let case_initialize =
225225+ Jsont.Object.Case.map "initialize" initialize_jsont ~dec:(fun v ->
226226+ Initialize v)
227227+ in
228228+ let case_set_permission_mode =
229229+ Jsont.Object.Case.map "set_permission_mode" set_permission_mode_jsont
230230+ ~dec:(fun v -> Set_permission_mode v)
231231+ in
232232+ let case_hook_callback =
233233+ Jsont.Object.Case.map "hook_callback" hook_callback_jsont ~dec:(fun v ->
234234+ Hook_callback v)
235235+ in
236236+ let case_mcp_message =
237237+ Jsont.Object.Case.map "mcp_message" mcp_message_jsont ~dec:(fun v ->
238238+ Mcp_message v)
239239+ in
240240+ let case_set_model =
241241+ Jsont.Object.Case.map "set_model" set_model_jsont ~dec:(fun v ->
242242+ Set_model v)
243243+ in
244244+ let case_get_server_info =
245245+ Jsont.Object.Case.map "get_server_info" get_server_info_jsont
246246+ ~dec:(fun v -> Get_server_info v)
247247+ in
194248195249 let enc_case = function
196250 | Interrupt v -> Jsont.Object.Case.value case_interrupt v
197251 | Permission v -> Jsont.Object.Case.value case_permission v
198252 | Initialize v -> Jsont.Object.Case.value case_initialize v
199199- | Set_permission_mode v -> Jsont.Object.Case.value case_set_permission_mode v
253253+ | Set_permission_mode v ->
254254+ Jsont.Object.Case.value case_set_permission_mode v
200255 | Hook_callback v -> Jsont.Object.Case.value case_hook_callback v
201256 | Mcp_message v -> Jsont.Object.Case.value case_mcp_message v
202257 | Set_model v -> Jsont.Object.Case.value case_set_model v
203258 | Get_server_info v -> Jsont.Object.Case.value case_get_server_info v
204259 in
205260206206- let cases = Jsont.Object.Case.[
207207- make case_interrupt;
208208- make case_permission;
209209- make case_initialize;
210210- make case_set_permission_mode;
211211- make case_hook_callback;
212212- make case_mcp_message;
213213- make case_set_model;
214214- make case_get_server_info;
215215- ] in
261261+ let cases =
262262+ Jsont.Object.Case.
263263+ [
264264+ make case_interrupt;
265265+ make case_permission;
266266+ make case_initialize;
267267+ make case_set_permission_mode;
268268+ make case_hook_callback;
269269+ make case_mcp_message;
270270+ make case_set_model;
271271+ make case_get_server_info;
272272+ ]
273273+ in
216274217275 Jsont.Object.map ~kind:"Request" Fun.id
218276 |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
219219- ~tag_to_string:Fun.id ~tag_compare:String.compare
277277+ ~tag_to_string:Fun.id ~tag_compare:String.compare
220278 |> Jsont.Object.finish
221279end
222280223281module Response = struct
224282 type success = {
225225- subtype : [`Success];
283283+ subtype : [ `Success ];
226284 request_id : string;
227285 response : Jsont.json option;
228286 unknown : Unknown.t;
229287 }
230288231289 type error = {
232232- subtype : [`Error];
290290+ subtype : [ `Error ];
233291 request_id : string;
234292 error : string;
235293 unknown : Unknown.t;
236294 }
237295238238- type t =
239239- | Success of success
240240- | Error of error
296296+ type t = Success of success | Error of error
241297242298 let success ~request_id ?response ?(unknown = Unknown.empty) () =
243243- Success {
244244- subtype = `Success;
245245- request_id;
246246- response;
247247- unknown;
248248- }
299299+ Success { subtype = `Success; request_id; response; unknown }
249300250301 let error ~request_id ~error ?(unknown = Unknown.empty) () =
251251- Error {
252252- subtype = `Error;
253253- request_id;
254254- error;
255255- unknown;
256256- }
302302+ Error { subtype = `Error; request_id; error; unknown }
257303258304 (* Individual record codecs *)
259305 let success_jsont : success Jsont.t =
···261307 { subtype = `Success; request_id; response; unknown }
262308 in
263309 Jsont.Object.map ~kind:"Success" make
264264- |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun (r : success) -> r.request_id)
265265- |> Jsont.Object.opt_mem "response" Jsont.json ~enc:(fun (r : success) -> r.response)
266266- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : success) -> r.unknown)
310310+ |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun (r : success) ->
311311+ r.request_id)
312312+ |> Jsont.Object.opt_mem "response" Jsont.json ~enc:(fun (r : success) ->
313313+ r.response)
314314+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : success) ->
315315+ r.unknown)
267316 |> Jsont.Object.finish
268317269318 let error_jsont : error Jsont.t =
···271320 { subtype = `Error; request_id; error; unknown }
272321 in
273322 Jsont.Object.map ~kind:"Error" make
274274- |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun (r : error) -> r.request_id)
323323+ |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun (r : error) ->
324324+ r.request_id)
275325 |> Jsont.Object.mem "error" Jsont.string ~enc:(fun (r : error) -> r.error)
276276- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : error) -> r.unknown)
326326+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : error) ->
327327+ r.unknown)
277328 |> Jsont.Object.finish
278329279330 (* Main variant codec using subtype discriminator *)
280331 let jsont : t Jsont.t =
281281- let case_success = Jsont.Object.Case.map "success" success_jsont ~dec:(fun v -> Success v) in
282282- let case_error = Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v) in
332332+ let case_success =
333333+ Jsont.Object.Case.map "success" success_jsont ~dec:(fun v -> Success v)
334334+ in
335335+ let case_error =
336336+ Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v)
337337+ in
283338284339 let enc_case = function
285340 | Success v -> Jsont.Object.Case.value case_success v
286341 | Error v -> Jsont.Object.Case.value case_error v
287342 in
288343289289- let cases = Jsont.Object.Case.[
290290- make case_success;
291291- make case_error;
292292- ] in
344344+ let cases = Jsont.Object.Case.[ make case_success; make case_error ] in
293345294346 Jsont.Object.map ~kind:"Response" Fun.id
295347 |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
296296- ~tag_to_string:Fun.id ~tag_compare:String.compare
348348+ ~tag_to_string:Fun.id ~tag_compare:String.compare
297349 |> Jsont.Object.finish
298350end
299351300352type control_request = {
301301- type_ : [`Control_request];
353353+ type_ : [ `Control_request ];
302354 request_id : string;
303355 request : Request.t;
304356 unknown : Unknown.t;
305357}
306358307359type control_response = {
308308- type_ : [`Control_response];
360360+ type_ : [ `Control_response ];
309361 response : Response.t;
310362 unknown : Unknown.t;
311363}
312364313313-type t =
314314- | Request of control_request
315315- | Response of control_response
365365+type t = Request of control_request | Response of control_response
316366317367let create_request ~request_id ~request ?(unknown = Unknown.empty) () =
318318- Request {
319319- type_ = `Control_request;
320320- request_id;
321321- request;
322322- unknown;
323323- }
368368+ Request { type_ = `Control_request; request_id; request; unknown }
324369325370let create_response ~response ?(unknown = Unknown.empty) () =
326326- Response {
327327- type_ = `Control_response;
328328- response;
329329- unknown;
330330- }
371371+ Response { type_ = `Control_response; response; unknown }
331372332373(* Individual record codecs *)
333374let control_request_jsont : control_request Jsont.t =
···335376 { type_ = `Control_request; request_id; request; unknown }
336377 in
337378 Jsont.Object.map ~kind:"ControlRequest" make
338338- |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun (r : control_request) -> r.request_id)
339339- |> Jsont.Object.mem "request" Request.jsont ~enc:(fun (r : control_request) -> r.request)
340340- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : control_request) -> r.unknown)
379379+ |> Jsont.Object.mem "request_id" Jsont.string
380380+ ~enc:(fun (r : control_request) -> r.request_id)
381381+ |> Jsont.Object.mem "request" Request.jsont ~enc:(fun (r : control_request) ->
382382+ r.request)
383383+ |> Jsont.Object.keep_unknown Jsont.json_mems
384384+ ~enc:(fun (r : control_request) -> r.unknown)
341385 |> Jsont.Object.finish
342386343387let control_response_jsont : control_response Jsont.t =
···345389 { type_ = `Control_response; response; unknown }
346390 in
347391 Jsont.Object.map ~kind:"ControlResponse" make
348348- |> Jsont.Object.mem "response" Response.jsont ~enc:(fun (r : control_response) -> r.response)
349349- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : control_response) -> r.unknown)
392392+ |> Jsont.Object.mem "response" Response.jsont
393393+ ~enc:(fun (r : control_response) -> r.response)
394394+ |> Jsont.Object.keep_unknown Jsont.json_mems
395395+ ~enc:(fun (r : control_response) -> r.unknown)
350396 |> Jsont.Object.finish
351397352398(* Main variant codec using type discriminator *)
353399let jsont : t Jsont.t =
354354- let case_request = Jsont.Object.Case.map "control_request" control_request_jsont ~dec:(fun v -> Request v) in
355355- let case_response = Jsont.Object.Case.map "control_response" control_response_jsont ~dec:(fun v -> Response v) in
400400+ let case_request =
401401+ Jsont.Object.Case.map "control_request" control_request_jsont ~dec:(fun v ->
402402+ Request v)
403403+ in
404404+ let case_response =
405405+ Jsont.Object.Case.map "control_response" control_response_jsont
406406+ ~dec:(fun v -> Response v)
407407+ in
356408357409 let enc_case = function
358410 | Request v -> Jsont.Object.Case.value case_request v
359411 | Response v -> Jsont.Object.Case.value case_response v
360412 in
361413362362- let cases = Jsont.Object.Case.[
363363- make case_request;
364364- make case_response;
365365- ] in
414414+ let cases = Jsont.Object.Case.[ make case_request; make case_response ] in
366415367416 Jsont.Object.map ~kind:"Control" Fun.id
368417 |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
369369- ~tag_to_string:Fun.id ~tag_compare:String.compare
418418+ ~tag_to_string:Fun.id ~tag_compare:String.compare
370419 |> Jsont.Object.finish
371420372421let log_request req =
373373- Log.debug (fun m -> m "SDK control request: %a" (Jsont.pp_value Request.jsont ()) req)
422422+ Log.debug (fun m ->
423423+ m "SDK control request: %a" (Jsont.pp_value Request.jsont ()) req)
374424375425let log_response resp =
376376- Log.debug (fun m -> m "SDK control response: %a" (Jsont.pp_value Response.jsont ()) resp)
426426+ Log.debug (fun m ->
427427+ m "SDK control response: %a" (Jsont.pp_value Response.jsont ()) resp)
377428378429(** Server information *)
379430module Server_info = struct
···385436 unknown : Unknown.t;
386437 }
387438388388- let create ~version ~capabilities ~commands ~output_styles ?(unknown = Unknown.empty) () =
439439+ let create ~version ~capabilities ~commands ~output_styles
440440+ ?(unknown = Unknown.empty) () =
389441 { version; capabilities; commands; output_styles; unknown }
390442391443 let version t = t.version
···395447 let unknown t = t.unknown
396448397449 let jsont : t Jsont.t =
398398- let make version capabilities commands output_styles (unknown : Unknown.t) : t =
450450+ let make version capabilities commands output_styles (unknown : Unknown.t) :
451451+ t =
399452 { version; capabilities; commands; output_styles; unknown }
400453 in
401454 Jsont.Object.map ~kind:"ServerInfo" make
402455 |> Jsont.Object.mem "version" Jsont.string ~enc:(fun (r : t) -> r.version)
403403- |> Jsont.Object.mem "capabilities" (Jsont.list Jsont.string) ~enc:(fun (r : t) -> r.capabilities) ~dec_absent:[]
404404- |> Jsont.Object.mem "commands" (Jsont.list Jsont.string) ~enc:(fun (r : t) -> r.commands) ~dec_absent:[]
405405- |> Jsont.Object.mem "outputStyles" (Jsont.list Jsont.string) ~enc:(fun (r : t) -> r.output_styles) ~dec_absent:[]
456456+ |> Jsont.Object.mem "capabilities" (Jsont.list Jsont.string)
457457+ ~enc:(fun (r : t) -> r.capabilities)
458458+ ~dec_absent:[]
459459+ |> Jsont.Object.mem "commands" (Jsont.list Jsont.string)
460460+ ~enc:(fun (r : t) -> r.commands)
461461+ ~dec_absent:[]
462462+ |> Jsont.Object.mem "outputStyles" (Jsont.list Jsont.string)
463463+ ~enc:(fun (r : t) -> r.output_styles)
464464+ ~dec_absent:[]
406465 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : t) -> r.unknown)
407466 |> Jsont.Object.finish
408408-end467467+end
+73-74
lib/sdk_control.mli
···10101111 {2 Protocol Overview}
12121313- The SDK control protocol is a JSON-based request/response protocol that
1414- runs alongside the main message stream. It enables:
1313+ The SDK control protocol is a JSON-based request/response protocol that runs
1414+ alongside the main message stream. It enables:
15151616- 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
1616+ 1. {b Callbacks}: Claude asks the SDK for permission or hook execution 2.
1717+ {b Control}: SDK changes Claude's behavior dynamically 3. {b Introspection}:
1818+ SDK queries server metadata
19192020 {2 Request/Response Flow}
2121···5151 See {!Client.set_permission_mode}, {!Client.set_model}, and
5252 {!Client.get_server_info} for high-level APIs that use this protocol. *)
53535454-(** The log source for SDK control operations *)
5554val src : Logs.Src.t
5555+(** The log source for SDK control operations *)
56565757(** {1 Request Types} *)
58585959module Request : sig
6060 (** SDK control request types. *)
61616262- type interrupt = {
6363- subtype : [`Interrupt];
6464- unknown : Unknown.t;
6565- }
6262+ type interrupt = { subtype : [ `Interrupt ]; unknown : Unknown.t }
6663 (** Interrupt request to stop execution. *)
67646865 type permission = {
6969- subtype : [`Can_use_tool];
6666+ subtype : [ `Can_use_tool ];
7067 tool_name : string;
7168 input : Jsont.json;
7269 permission_suggestions : Permissions.Update.t list option;
···7673 (** Permission request for tool usage. *)
77747875 type initialize = {
7979- subtype : [`Initialize];
8080- hooks : (string * Jsont.json) list option; (* Hook event to configuration *)
7676+ subtype : [ `Initialize ];
7777+ hooks : (string * Jsont.json) list option; (* Hook event to configuration *)
8178 unknown : Unknown.t;
8279 }
8380 (** Initialize request with optional hook configuration. *)
84818582 type set_permission_mode = {
8686- subtype : [`Set_permission_mode];
8383+ subtype : [ `Set_permission_mode ];
8784 mode : Permissions.Mode.t;
8885 unknown : Unknown.t;
8986 }
9087 (** Request to change permission mode. *)
91889289 type hook_callback = {
9393- subtype : [`Hook_callback];
9090+ subtype : [ `Hook_callback ];
9491 callback_id : string;
9592 input : Jsont.json;
9693 tool_use_id : string option;
···9996 (** Hook callback request. *)
1009710198 type mcp_message = {
102102- subtype : [`Mcp_message];
9999+ subtype : [ `Mcp_message ];
103100 server_name : string;
104101 message : Jsont.json;
105102 unknown : Unknown.t;
···107104 (** MCP server message request. *)
108105109106 type set_model = {
110110- subtype : [`Set_model];
107107+ subtype : [ `Set_model ];
111108 model : string;
112109 unknown : Unknown.t;
113110 }
114111 (** Request to change the AI model. *)
115112116116- type get_server_info = {
117117- subtype : [`Get_server_info];
118118- unknown : Unknown.t;
119119- }
113113+ type get_server_info = { subtype : [ `Get_server_info ]; unknown : Unknown.t }
120114 (** Request to get server information. *)
121115122116 type t =
···128122 | Mcp_message of mcp_message
129123 | Set_model of set_model
130124 | Get_server_info of get_server_info
131131- (** The type of SDK control requests. *)
125125+ (** The type of SDK control requests. *)
132126133127 val interrupt : ?unknown:Unknown.t -> unit -> t
134128 (** [interrupt ?unknown ()] creates an interrupt request. *)
···139133 ?permission_suggestions:Permissions.Update.t list ->
140134 ?blocked_path:string ->
141135 ?unknown:Unknown.t ->
142142- unit -> t
143143- (** [permission ~tool_name ~input ?permission_suggestions ?blocked_path ?unknown ()]
144144- creates a permission request. *)
136136+ unit ->
137137+ t
138138+ (** [permission ~tool_name ~input ?permission_suggestions ?blocked_path
139139+ ?unknown ()] creates a permission request. *)
145140146146- val initialize : ?hooks:(string * Jsont.json) list -> ?unknown:Unknown.t -> unit -> t
141141+ val initialize :
142142+ ?hooks:(string * Jsont.json) list -> ?unknown:Unknown.t -> unit -> t
147143 (** [initialize ?hooks ?unknown ()] creates an initialize request. *)
148144149149- val set_permission_mode : mode:Permissions.Mode.t -> ?unknown:Unknown.t -> unit -> t
150150- (** [set_permission_mode ~mode ?unknown] creates a permission mode change request. *)
145145+ val set_permission_mode :
146146+ mode:Permissions.Mode.t -> ?unknown:Unknown.t -> unit -> t
147147+ (** [set_permission_mode ~mode ?unknown] creates a permission mode change
148148+ request. *)
151149152150 val hook_callback :
153151 callback_id:string ->
154152 input:Jsont.json ->
155153 ?tool_use_id:string ->
156154 ?unknown:Unknown.t ->
157157- unit -> t
158158- (** [hook_callback ~callback_id ~input ?tool_use_id ?unknown ()] creates a hook callback request. *)
155155+ unit ->
156156+ t
157157+ (** [hook_callback ~callback_id ~input ?tool_use_id ?unknown ()] creates a
158158+ hook callback request. *)
159159160160- val mcp_message : server_name:string -> message:Jsont.json -> ?unknown:Unknown.t -> unit -> t
161161- (** [mcp_message ~server_name ~message ?unknown] creates an MCP message request. *)
160160+ val mcp_message :
161161+ server_name:string -> message:Jsont.json -> ?unknown:Unknown.t -> unit -> t
162162+ (** [mcp_message ~server_name ~message ?unknown] creates an MCP message
163163+ request. *)
162164163165 val set_model : model:string -> ?unknown:Unknown.t -> unit -> t
164166 (** [set_model ~model ?unknown] creates a model change request. *)
···167169 (** [get_server_info ?unknown ()] creates a server info request. *)
168170169171 val jsont : t Jsont.t
170170- (** [jsont] is the jsont codec for requests.
171171- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
172172+ (** [jsont] is the jsont codec for requests. Use [Jsont.pp_value jsont ()] for
173173+ pretty-printing. *)
172174end
173175174176(** {1 Response Types} *)
···177179 (** SDK control response types. *)
178180179181 type success = {
180180- subtype : [`Success];
182182+ subtype : [ `Success ];
181183 request_id : string;
182184 response : Jsont.json option;
183185 unknown : Unknown.t;
···185187 (** Successful response. *)
186188187189 type error = {
188188- subtype : [`Error];
190190+ subtype : [ `Error ];
189191 request_id : string;
190192 error : string;
191193 unknown : Unknown.t;
···194196195197 type t =
196198 | Success of success
197197- | Error of error
198198- (** The type of SDK control responses. *)
199199+ | Error of error (** The type of SDK control responses. *)
199200200200- val success : request_id:string -> ?response:Jsont.json -> ?unknown:Unknown.t -> unit -> t
201201+ val success :
202202+ request_id:string -> ?response:Jsont.json -> ?unknown:Unknown.t -> unit -> t
201203 (** [success ~request_id ?response ?unknown ()] creates a success response. *)
202204203203- val error : request_id:string -> error:string -> ?unknown:Unknown.t -> unit -> t
205205+ val error :
206206+ request_id:string -> error:string -> ?unknown:Unknown.t -> unit -> t
204207 (** [error ~request_id ~error ?unknown] creates an error response. *)
205208206209 val jsont : t Jsont.t
207207- (** [jsont] is the jsont codec for responses.
208208- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
210210+ (** [jsont] is the jsont codec for responses. Use [Jsont.pp_value jsont ()]
211211+ for pretty-printing. *)
209212end
210213211214(** {1 Control Messages} *)
212215213216type control_request = {
214214- type_ : [`Control_request];
217217+ type_ : [ `Control_request ];
215218 request_id : string;
216219 request : Request.t;
217220 unknown : Unknown.t;
···219222(** Control request message. *)
220223221224type control_response = {
222222- type_ : [`Control_response];
225225+ type_ : [ `Control_response ];
223226 response : Response.t;
224227 unknown : Unknown.t;
225228}
226229(** Control response message. *)
227230228231val control_response_jsont : control_response Jsont.t
229229-(** [control_response_jsont] is the jsont codec for control response messages. *)
232232+(** [control_response_jsont] is the jsont codec for control response messages.
233233+*)
230234231235type t =
232236 | Request of control_request
233233- | Response of control_response
234234-(** The type of SDK control messages. *)
237237+ | Response of control_response (** The type of SDK control messages. *)
235238236236-val create_request : request_id:string -> request:Request.t -> ?unknown:Unknown.t -> unit -> t
237237-(** [create_request ~request_id ~request ?unknown ()] creates a control request message. *)
239239+val create_request :
240240+ request_id:string -> request:Request.t -> ?unknown:Unknown.t -> unit -> t
241241+(** [create_request ~request_id ~request ?unknown ()] creates a control request
242242+ message. *)
238243239244val create_response : response:Response.t -> ?unknown:Unknown.t -> unit -> t
240240-(** [create_response ~response ?unknown ()] creates a control response message. *)
245245+(** [create_response ~response ?unknown ()] creates a control response message.
246246+*)
241247242248val jsont : t Jsont.t
243243-(** [jsont] is the jsont codec for control messages.
244244- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
249249+(** [jsont] is the jsont codec for control messages. Use
250250+ [Jsont.pp_value jsont ()] for pretty-printing. *)
245251246252(** {1 Logging} *)
247253···253259254260(** {1 Server Information}
255261256256- Server information provides metadata about the Claude CLI server,
257257- including version, capabilities, available commands, and output styles.
262262+ Server information provides metadata about the Claude CLI server, including
263263+ version, capabilities, available commands, and output styles.
258264259265 {2 Use Cases}
260266···267273268274 {[
269275 let info = Client.get_server_info client in
270270- Printf.printf "Claude CLI version: %s\n"
271271- (Server_info.version info);
276276+ Printf.printf "Claude CLI version: %s\n" (Server_info.version info);
272277273278 if List.mem "structured-output" (Server_info.capabilities info) then
274279 Printf.printf "Structured output is supported\n"
275275- else
276276- Printf.printf "Structured output not available\n";
280280+ else Printf.printf "Structured output not available\n"
277281 ]} *)
278282279283module Server_info : sig
280284 (** Server information and capabilities. *)
281285282286 type t = {
283283- version : string;
284284- (** Server version string (e.g., "2.0.0") *)
285285-287287+ version : string; (** Server version string (e.g., "2.0.0") *)
286288 capabilities : string list;
287287- (** Available server capabilities (e.g., "hooks", "structured-output") *)
288288-289289- commands : string list;
290290- (** Available CLI commands *)
291291-289289+ (** Available server capabilities (e.g., "hooks", "structured-output")
290290+ *)
291291+ commands : string list; (** Available CLI commands *)
292292 output_styles : string list;
293293- (** Supported output formats (e.g., "json", "stream-json") *)
294294-295295- unknown : Unknown.t;
296296- (** Unknown fields for forward compatibility *)
293293+ (** Supported output formats (e.g., "json", "stream-json") *)
294294+ unknown : Unknown.t; (** Unknown fields for forward compatibility *)
297295 }
298296 (** Server metadata and capabilities.
299297···307305 ?unknown:Unknown.t ->
308306 unit ->
309307 t
310310- (** [create ~version ~capabilities ~commands ~output_styles ?unknown ()] creates server info. *)
308308+ (** [create ~version ~capabilities ~commands ~output_styles ?unknown ()]
309309+ creates server info. *)
311310312311 val version : t -> string
313312 (** [version t] returns the server version. *)
···325324 (** [unknown t] returns the unknown fields. *)
326325327326 val jsont : t Jsont.t
328328- (** [jsont] is the jsont codec for server info.
329329- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
330330-end327327+ (** [jsont] is the jsont codec for server info. Use [Jsont.pp_value jsont ()]
328328+ for pretty-printing. *)
329329+end
+5-7
lib/structured_output.ml
···11let src = Logs.Src.create "claude.structured_output" ~doc:"Structured output"
22+23module Log = (val Logs.src_log src : Logs.LOG)
3444-type t = {
55- json_schema : Jsont.json;
66-}
55+type t = { json_schema : Jsont.json }
7687let json_to_string json =
98 match Jsont_bytesrw.encode_string' Jsont.json json with
···1110 | Error err -> failwith (Jsont.Error.to_string err)
12111312let of_json_schema schema =
1414- Log.debug (fun m -> m "Created output format from JSON schema: %s"
1515- (json_to_string schema));
1313+ Log.debug (fun m ->
1414+ m "Created output format from JSON schema: %s" (json_to_string schema));
1615 { json_schema = schema }
17161817let json_schema t = t.json_schema
19182019(* Codec for serializing structured output format *)
2120let jsont : t Jsont.t =
2222- Jsont.Object.map ~kind:"StructuredOutput"
2323- (fun json_schema -> {json_schema})
2121+ Jsont.Object.map ~kind:"StructuredOutput" (fun json_schema -> { json_schema })
2422 |> Jsont.Object.mem "jsonSchema" Jsont.json ~enc:(fun t -> t.json_schema)
2523 |> Jsont.Object.finish
2624
+27-35
lib/structured_output.mli
···7788 {2 Overview}
991010- Structured outputs ensure that Claude's responses conform to a specific
1111- JSON schema, making it easier to parse and use the results programmatically.
1212- This is particularly useful for:
1010+ Structured outputs ensure that Claude's responses conform to a specific JSON
1111+ schema, making it easier to parse and use the results programmatically. This
1212+ is particularly useful for:
13131414 - Extracting structured data from unstructured text
1515 - Building APIs that require consistent JSON responses
···42424343 {3 Helper Functions for Building Schemas}
44444545- For complex schemas, you can use helper functions to make construction easier:
4545+ For complex schemas, you can use helper functions to make construction
4646+ easier:
4647 {[
4747- let json_object fields =
4848- Jsont.Object (fields, Jsont.Meta.none)
4949-5050- let json_string s =
5151- Jsont.String (s, Jsont.Meta.none)
5252-5353- let json_array items =
5454- Jsont.Array (items, Jsont.Meta.none)
5555-5656- let json_field name value =
5757- ((name, Jsont.Meta.none), value)
4848+ let json_object fields = Jsont.Object (fields, Jsont.Meta.none)
4949+ let json_string s = Jsont.String (s, Jsont.Meta.none)
5050+ let json_array items = Jsont.Array (items, Jsont.Meta.none)
5151+ let json_field name value = ((name, Jsont.Meta.none), value)
58525953 let person_schema =
6060- json_object [
6161- json_field "type" (json_string "object");
6262- json_field "properties" (json_object [
6363- json_field "name" (json_object [
6464- json_field "type" (json_string "string")
6565- ]);
6666- json_field "age" (json_object [
6767- json_field "type" (json_string "integer")
6868- ]);
6969- ]);
7070- json_field "required" (json_array [
7171- json_string "name";
7272- json_string "age"
7373- ])
7474- ]
5454+ json_object
5555+ [
5656+ json_field "type" (json_string "object");
5757+ json_field "properties"
5858+ (json_object
5959+ [
6060+ json_field "name"
6161+ (json_object [ json_field "type" (json_string "string") ]);
6262+ json_field "age"
6363+ (json_object [ json_field "type" (json_string "integer") ]);
6464+ ]);
6565+ json_field "required"
6666+ (json_array [ json_string "name"; json_string "age" ]);
6767+ ]
75687669 let format = Structured_output.of_json_schema person_schema
7770 ]}
···113106 @see <https://json-schema.org/> JSON Schema specification
114107 @see <https://erratique.ch/software/jsont> jsont documentation *)
115108116116-(** The log source for structured output operations *)
117109val src : Logs.Src.t
110110+(** The log source for structured output operations *)
118111119112(** {1 Output Format Configuration} *)
120113···159152 Internal use for encoding/decoding with the CLI. *)
160153161154val to_json : t -> Jsont.json
162162-(** [to_json t] converts the output format to its JSON representation.
163163- Internal use only. *)
155155+(** [to_json t] converts the output format to its JSON representation. Internal
156156+ use only. *)
164157165158val of_json : Jsont.json -> t
166166-(** [of_json json] parses an output format from JSON.
167167- Internal use only.
159159+(** [of_json json] parses an output format from JSON. Internal use only.
168160 @raise Invalid_argument if the JSON is not a valid output format. *)
+129-84
lib/transport.ml
···11open Eio.Std
2233let src = Logs.Src.create "claude.transport" ~doc:"Claude transport layer"
44+45module Log = (val Logs.src_log src : Logs.LOG)
5667exception CLI_not_found of string
···1213type t = {
1314 process : process;
1415 stdin : Eio.Flow.sink_ty r;
1515- stdin_close : [`Close | `Flow] r;
1616+ stdin_close : [ `Close | `Flow ] r;
1617 stdout : Eio.Buf_read.t;
1718}
1819···2223 | Options.Local -> "local"
23242425let build_command ~claude_path ~options =
2525- let cmd = [claude_path; "--output-format"; "stream-json"; "--verbose"] in
2626+ let cmd = [ claude_path; "--output-format"; "stream-json"; "--verbose" ] in
26272727- let cmd = match Options.system_prompt options with
2828- | Some prompt -> cmd @ ["--system-prompt"; prompt]
2828+ let cmd =
2929+ match Options.system_prompt options with
3030+ | Some prompt -> cmd @ [ "--system-prompt"; prompt ]
2931 | None -> cmd
3032 in
31333232- let cmd = match Options.append_system_prompt options with
3333- | Some prompt -> cmd @ ["--append-system-prompt"; prompt]
3434+ let cmd =
3535+ match Options.append_system_prompt options with
3636+ | Some prompt -> cmd @ [ "--append-system-prompt"; prompt ]
3437 | None -> cmd
3538 in
36393737- let cmd = match Options.allowed_tools options with
4040+ let cmd =
4141+ match Options.allowed_tools options with
3842 | [] -> cmd
3939- | tools -> cmd @ ["--allowedTools"; String.concat "," tools]
4343+ | tools -> cmd @ [ "--allowedTools"; String.concat "," tools ]
4044 in
41454242- let cmd = match Options.disallowed_tools options with
4646+ let cmd =
4747+ match Options.disallowed_tools options with
4348 | [] -> cmd
4444- | tools -> cmd @ ["--disallowedTools"; String.concat "," tools]
4949+ | tools -> cmd @ [ "--disallowedTools"; String.concat "," tools ]
4550 in
46514747- let cmd = match Options.model options with
4848- | Some model -> cmd @ ["--model"; Model.to_string model]
5252+ let cmd =
5353+ match Options.model options with
5454+ | Some model -> cmd @ [ "--model"; Model.to_string model ]
4955 | None -> cmd
5056 in
51575252- let cmd = match Options.permission_mode options with
5858+ let cmd =
5959+ match Options.permission_mode options with
5360 | Some mode ->
5461 let mode_str = Permissions.Mode.to_string mode in
5555- cmd @ ["--permission-mode"; mode_str]
6262+ cmd @ [ "--permission-mode"; mode_str ]
5663 | None -> cmd
5764 in
58655959- let cmd = match Options.permission_prompt_tool_name options with
6060- | Some tool_name -> cmd @ ["--permission-prompt-tool"; tool_name]
6666+ let cmd =
6767+ match Options.permission_prompt_tool_name options with
6868+ | Some tool_name -> cmd @ [ "--permission-prompt-tool"; tool_name ]
6169 | None -> cmd
6270 in
63716472 (* Advanced configuration options *)
6565- let cmd = match Options.max_budget_usd options with
6666- | Some budget -> cmd @ ["--max-budget-usd"; Float.to_string budget]
7373+ let cmd =
7474+ match Options.max_budget_usd options with
7575+ | Some budget -> cmd @ [ "--max-budget-usd"; Float.to_string budget ]
6776 | None -> cmd
6877 in
69787070- let cmd = match Options.fallback_model options with
7171- | Some model -> cmd @ ["--fallback-model"; Model.to_string model]
7979+ let cmd =
8080+ match Options.fallback_model options with
8181+ | Some model -> cmd @ [ "--fallback-model"; Model.to_string model ]
7282 | None -> cmd
7383 in
74847575- let cmd = match Options.setting_sources options with
8585+ let cmd =
8686+ match Options.setting_sources options with
7687 | Some sources ->
7777- let sources_str = String.concat "," (List.map setting_source_to_string sources) in
7878- cmd @ ["--setting-sources"; sources_str]
8888+ let sources_str =
8989+ String.concat "," (List.map setting_source_to_string sources)
9090+ in
9191+ cmd @ [ "--setting-sources"; sources_str ]
7992 | None -> cmd
8093 in
81948295 (* Add JSON Schema if specified *)
8383- let cmd = match Options.output_format options with
9696+ let cmd =
9797+ match Options.output_format options with
8498 | Some format ->
8599 let schema = Structured_output.json_schema format in
8686- let schema_str = match Jsont_bytesrw.encode_string' Jsont.json schema with
100100+ let schema_str =
101101+ match Jsont_bytesrw.encode_string' Jsont.json schema with
87102 | Ok s -> s
88103 | Error err -> failwith (Jsont.Error.to_string err)
89104 in
9090- cmd @ ["--json-schema"; schema_str]
105105+ cmd @ [ "--json-schema"; schema_str ]
91106 | None -> cmd
92107 in
9310894109 (* Use streaming input mode *)
9595- cmd @ ["--input-format"; "stream-json"]
110110+ cmd @ [ "--input-format"; "stream-json" ]
9611197112let create ~sw ~process_mgr ~options () =
98113 let claude_path = "claude" in
99114 let cmd = build_command ~claude_path ~options in
100100-115115+101116 (* Build environment - preserve essential vars for Claude config/auth access *)
102117 let home = try Unix.getenv "HOME" with Not_found -> "/tmp" in
103118 let path = try Unix.getenv "PATH" with Not_found -> "/usr/bin:/bin" in
104104-119119+105120 (* Preserve other potentially important environment variables *)
106106- let preserve_vars = [
107107- "USER"; "LOGNAME"; "SHELL"; "TERM";
108108- "XDG_CONFIG_HOME"; "XDG_DATA_HOME"; "XDG_CACHE_HOME";
109109- "ANTHROPIC_API_KEY"; "CLAUDE_API_KEY" (* In case API key is set via env *)
110110- ] in
111111-112112- let preserved = List.filter_map (fun var ->
113113- try Some (Printf.sprintf "%s=%s" var (Unix.getenv var))
114114- with Not_found -> None
115115- ) preserve_vars in
116116-117117- let base_env = [
118118- Printf.sprintf "HOME=%s" home;
119119- Printf.sprintf "PATH=%s" path;
120120- "CLAUDE_CODE_ENTRYPOINT=sdk-ocaml";
121121- ] @ preserved in
122122-123123- let custom_env = List.map (fun (k, v) -> Printf.sprintf "%s=%s" k v) (Options.env options) in
121121+ let preserve_vars =
122122+ [
123123+ "USER";
124124+ "LOGNAME";
125125+ "SHELL";
126126+ "TERM";
127127+ "XDG_CONFIG_HOME";
128128+ "XDG_DATA_HOME";
129129+ "XDG_CACHE_HOME";
130130+ "ANTHROPIC_API_KEY";
131131+ "CLAUDE_API_KEY" (* In case API key is set via env *);
132132+ ]
133133+ in
134134+135135+ let preserved =
136136+ List.filter_map
137137+ (fun var ->
138138+ try Some (Printf.sprintf "%s=%s" var (Unix.getenv var))
139139+ with Not_found -> None)
140140+ preserve_vars
141141+ in
142142+143143+ let base_env =
144144+ [
145145+ Printf.sprintf "HOME=%s" home;
146146+ Printf.sprintf "PATH=%s" path;
147147+ "CLAUDE_CODE_ENTRYPOINT=sdk-ocaml";
148148+ ]
149149+ @ preserved
150150+ in
151151+152152+ let custom_env =
153153+ List.map (fun (k, v) -> Printf.sprintf "%s=%s" k v) (Options.env options)
154154+ in
124155 let env = Array.of_list (base_env @ custom_env) in
125156 Log.debug (fun m -> m "Environment: HOME=%s, PATH=%s" home path);
126126- Log.info (fun m -> m "Full environment variables: %s" (String.concat ", " (Array.to_list env)));
127127-157157+ Log.info (fun m ->
158158+ m "Full environment variables: %s"
159159+ (String.concat ", " (Array.to_list env)));
160160+128161 let stdin_r, stdin_w = Eio.Process.pipe ~sw process_mgr in
129162 let stdout_r, stdout_w = Eio.Process.pipe ~sw process_mgr in
130163 let stderr_r, stderr_w = Eio.Process.pipe ~sw process_mgr in
131164 (* Close stderr pipes - we don't need them *)
132165 Eio.Flow.close stderr_r;
133166 Eio.Flow.close stderr_w;
134134-135135- let process =
167167+168168+ let process =
136169 try
137137- Log.info (fun m -> m "Spawning claude with command: %s" (String.concat " " cmd));
170170+ Log.info (fun m ->
171171+ m "Spawning claude with command: %s" (String.concat " " cmd));
138172 Log.info (fun m -> m "Command arguments breakdown:");
139139- List.iteri (fun i arg ->
140140- Log.info (fun m -> m " [%d]: %s" i arg)
141141- ) cmd;
142142- Eio.Process.spawn ~sw process_mgr
143143- ~env
173173+ List.iteri (fun i arg -> Log.info (fun m -> m " [%d]: %s" i arg)) cmd;
174174+ Eio.Process.spawn ~sw process_mgr ~env
144175 ~stdin:(stdin_r :> Eio.Flow.source_ty r)
145176 ~stdout:(stdout_w :> Eio.Flow.sink_ty r)
146146- ?cwd:(Options.cwd options)
147147- cmd
148148- with
149149- | exn ->
150150- Log.err (fun m -> m "Failed to spawn claude CLI: %s" (Printexc.to_string exn));
151151- Log.err (fun m -> m "Make sure 'claude' is installed and authenticated");
152152- Log.err (fun m -> m "You may need to run 'claude login' first");
153153- raise (CLI_not_found (Printf.sprintf "Failed to spawn claude CLI: %s" (Printexc.to_string exn)))
177177+ ?cwd:(Options.cwd options) cmd
178178+ with exn ->
179179+ Log.err (fun m ->
180180+ m "Failed to spawn claude CLI: %s" (Printexc.to_string exn));
181181+ Log.err (fun m -> m "Make sure 'claude' is installed and authenticated");
182182+ Log.err (fun m -> m "You may need to run 'claude login' first");
183183+ raise
184184+ (CLI_not_found
185185+ (Printf.sprintf "Failed to spawn claude CLI: %s"
186186+ (Printexc.to_string exn)))
154187 in
155155-188188+156189 let stdin = (stdin_w :> Eio.Flow.sink_ty r) in
157157- let stdin_close = (stdin_w :> [`Close | `Flow] r) in
158158- let max_size = match Options.max_buffer_size options with
190190+ let stdin_close = (stdin_w :> [ `Close | `Flow ] r) in
191191+ let max_size =
192192+ match Options.max_buffer_size options with
159193 | Some size -> size
160160- | None -> 1_000_000 (* Default 1MB *)
194194+ | None -> 1_000_000 (* Default 1MB *)
195195+ in
196196+ let stdout =
197197+ Eio.Buf_read.of_flow ~max_size (stdout_r :> Eio.Flow.source_ty r)
161198 in
162162- let stdout = Eio.Buf_read.of_flow ~max_size (stdout_r :> Eio.Flow.source_ty r) in
163199164200 { process = P process; stdin; stdin_close; stdout }
165201166202let send t json =
167167- let data = match Jsont_bytesrw.encode_string' Jsont.json json with
203203+ let data =
204204+ match Jsont_bytesrw.encode_string' Jsont.json json with
168205 | Ok s -> s
169206 | Error err -> failwith (Jsont.Error.to_string err)
170207 in
171208 Log.debug (fun m -> m "Sending: %s" data);
172172- try
173173- Eio.Flow.write t.stdin [Cstruct.of_string (data ^ "\n")]
174174- with
175175- | exn ->
176176- Log.err (fun m -> m "Failed to send message: %s" (Printexc.to_string exn));
177177- raise (Connection_error (Printf.sprintf "Failed to send message: %s" (Printexc.to_string exn)))
209209+ try Eio.Flow.write t.stdin [ Cstruct.of_string (data ^ "\n") ]
210210+ with exn ->
211211+ Log.err (fun m -> m "Failed to send message: %s" (Printexc.to_string exn));
212212+ raise
213213+ (Connection_error
214214+ (Printf.sprintf "Failed to send message: %s" (Printexc.to_string exn)))
178215179216let receive_line t =
180217 try
181218 match Eio.Buf_read.line t.stdout with
182182- | line ->
219219+ | line ->
183220 Log.debug (fun m -> m "Raw JSON: %s" line);
184221 Some line
185185- | exception End_of_file ->
222222+ | exception End_of_file ->
186223 Log.debug (fun m -> m "Received EOF");
187224 None
188188- with
189189- | exn ->
190190- Log.err (fun m -> m "Failed to receive message: %s" (Printexc.to_string exn));
191191- raise (Connection_error (Printf.sprintf "Failed to receive message: %s" (Printexc.to_string exn)))
225225+ with exn ->
226226+ Log.err (fun m ->
227227+ m "Failed to receive message: %s" (Printexc.to_string exn));
228228+ raise
229229+ (Connection_error
230230+ (Printf.sprintf "Failed to receive message: %s"
231231+ (Printexc.to_string exn)))
192232193233(** Wire codec for interrupt response messages. *)
194234module Interrupt_wire = struct
···210250 |> Jsont.Object.finish
211251212252 let encode () =
213213- let wire = { type_ = "control_response"; response = { subtype = "interrupt"; request_id = "" } } in
253253+ let wire =
254254+ {
255255+ type_ = "control_response";
256256+ response = { subtype = "interrupt"; request_id = "" };
257257+ }
258258+ in
214259 match Jsont.Json.encode jsont wire with
215260 | Ok json -> json
216261 | Error msg -> failwith ("Interrupt_wire.encode: " ^ msg)
+3-2
lib/transport.mli
···11-(** The log source for transport operations *)
21val src : Logs.Src.t
22+(** The log source for transport operations *)
3344exception CLI_not_found of string
55exception Process_error of string
···1111 sw:Eio.Switch.t ->
1212 process_mgr:_ Eio.Process.mgr ->
1313 options:Options.t ->
1414- unit -> t
1414+ unit ->
1515+ t
15161617val send : t -> Jsont.json -> unit
1718val receive_line : t -> string option
+5-7
lib/unknown.ml
···2233 This module provides a type and utilities for preserving unknown/extra
44 fields when parsing JSON objects with jsont. Use with
55- [Jsont.Object.keep_unknown] to capture fields not explicitly defined
66- in your codec. *)
55+ [Jsont.Object.keep_unknown] to capture fields not explicitly defined in your
66+ codec. *)
7788type t = Jsont.json
99(** The type of unknown fields - stored as raw JSON. *)
10101111-let empty = Jsont.Object ([], Jsont.Meta.none)
1211(** An empty unknown fields value (empty JSON object). *)
1212+let empty = Jsont.Object ([], Jsont.Meta.none)
13131414-let is_empty = function
1515- | Jsont.Object ([], _) -> true
1616- | _ -> false
1714(** [is_empty t] returns [true] if there are no unknown fields. *)
1515+let is_empty = function Jsont.Object ([], _) -> true | _ -> false
18161717+(** Codec for unknown fields. *)
1918let jsont = Jsont.json
2020-(** Codec for unknown fields. *)
+2-2
lib/unknown.mli
···2233 This module provides a type and utilities for preserving unknown/extra
44 fields when parsing JSON objects with jsont. Use with
55- [Jsont.Object.keep_unknown] to capture fields not explicitly defined
66- in your codec. *)
55+ [Jsont.Object.keep_unknown] to capture fields not explicitly defined in your
66+ codec. *)
7788type t = Jsont.json
99(** The type of unknown fields - stored as raw JSON. *)
+48-44
test/advanced_config_demo.ml
···2121 without any user/project/local settings interfering.
2222*)
2323let ci_cd_config () =
2424- Options.default
2525- |> Options.with_no_settings (* Disable all settings loading *)
2626- |> Options.with_max_budget_usd 0.50 (* 50 cent limit per run *)
2727- |> Options.with_fallback_model_string "claude-haiku-4" (* Fast fallback *)
2424+ Options.default |> Options.with_no_settings (* Disable all settings loading *)
2525+ |> Options.with_max_budget_usd 0.50 (* 50 cent limit per run *)
2626+ |> Options.with_fallback_model_string "claude-haiku-4" (* Fast fallback *)
2827 |> Options.with_model_string "claude-sonnet-4-5"
2928 |> Options.with_permission_mode Permissions.Mode.Bypass_permissions
3029···3736 Options.default
3837 |> Options.with_model_string "claude-sonnet-4-5"
3938 |> Options.with_fallback_model_string "claude-sonnet-3-5"
4040- |> Options.with_max_budget_usd 10.0 (* $10 limit *)
4141- |> Options.with_max_buffer_size 5_000_000 (* 5MB buffer for large outputs *)
3939+ |> Options.with_max_budget_usd 10.0 (* $10 limit *)
4040+ |> Options.with_max_buffer_size 5_000_000 (* 5MB buffer for large outputs *)
42414342(* Example 3: Development Configuration
4443···4645*)
4746let dev_config () =
4847 Options.default
4949- |> Options.with_setting_sources [Options.User; Options.Project]
5050- |> Options.with_max_budget_usd 1.0 (* $1 limit for dev testing *)
4848+ |> Options.with_setting_sources [ Options.User; Options.Project ]
4949+ |> Options.with_max_budget_usd 1.0 (* $1 limit for dev testing *)
5150 |> Options.with_fallback_model_string "claude-haiku-4"
52515352(* Example 4: Isolated Test Configuration
···5554 For automated testing with no external settings and strict limits.
5655*)
5756let test_config () =
5858- Options.default
5959- |> Options.with_no_settings
6060- |> Options.with_max_budget_usd 0.10 (* 10 cent limit per test *)
6161- |> Options.with_model_string "claude-haiku-4" (* Fast, cheap model *)
5757+ Options.default |> Options.with_no_settings
5858+ |> Options.with_max_budget_usd 0.10 (* 10 cent limit per test *)
5959+ |> Options.with_model_string "claude-haiku-4" (* Fast, cheap model *)
6260 |> Options.with_permission_mode Permissions.Mode.Bypass_permissions
6363- |> Options.with_max_buffer_size 1_000_000 (* 1MB buffer *)
6161+ |> Options.with_max_buffer_size 1_000_000 (* 1MB buffer *)
64626563(* Example 5: Custom Buffer Size Demo
6664···6866*)
6967let _large_output_config () =
7068 Options.default
7171- |> Options.with_max_buffer_size 10_000_000 (* 10MB buffer *)
6969+ |> Options.with_max_buffer_size 10_000_000 (* 10MB buffer *)
7270 |> Options.with_model_string "claude-sonnet-4-5"
73717472(* Helper to run a query with a specific configuration *)
···7876 | Some budget -> Printf.printf "Budget limit: $%.2f\n" budget
7977 | None -> print_endline "Budget limit: None");
8078 (match Options.fallback_model config with
8181- | Some model -> Printf.printf "Fallback model: %s\n" (Claude.Model.to_string model)
7979+ | Some model ->
8080+ Printf.printf "Fallback model: %s\n" (Claude.Model.to_string model)
8281 | None -> print_endline "Fallback model: None");
8382 (match Options.setting_sources config with
8483 | Some [] -> print_endline "Settings: Isolated (no settings loaded)"
8584 | Some sources ->
8686- let source_str = String.concat ", " (List.map (function
8787- | Options.User -> "user"
8888- | Options.Project -> "project"
8989- | Options.Local -> "local"
9090- ) sources) in
8585+ let source_str =
8686+ String.concat ", "
8787+ (List.map
8888+ (function
8989+ | Options.User -> "user"
9090+ | Options.Project -> "project"
9191+ | Options.Local -> "local")
9292+ sources)
9393+ in
9194 Printf.printf "Settings: %s\n" source_str
9295 | None -> print_endline "Settings: Default");
9396 (match Options.max_buffer_size config with
···99102 Client.query client prompt;
100103 let messages = Client.receive client in
101104102102- Seq.iter (function
103103- | Message.Assistant msg ->
104104- List.iter (function
105105- | Content_block.Text t ->
106106- Printf.printf "Response: %s\n" (Content_block.Text.text t)
107107- | _ -> ()
108108- ) (Message.Assistant.content msg)
109109- | Message.Result result ->
110110- Printf.printf "\n=== Session Complete ===\n";
111111- Printf.printf "Duration: %dms\n" (Message.Result.duration_ms result);
112112- (match Message.Result.total_cost_usd result with
113113- | Some cost -> Printf.printf "Cost: $%.4f\n" cost
114114- | None -> ());
115115- Printf.printf "Turns: %d\n" (Message.Result.num_turns result)
116116- | _ -> ()
117117- ) messages
105105+ Seq.iter
106106+ (function
107107+ | Message.Assistant msg ->
108108+ List.iter
109109+ (function
110110+ | Content_block.Text t ->
111111+ Printf.printf "Response: %s\n" (Content_block.Text.text t)
112112+ | _ -> ())
113113+ (Message.Assistant.content msg)
114114+ | Message.Result result ->
115115+ Printf.printf "\n=== Session Complete ===\n";
116116+ Printf.printf "Duration: %dms\n" (Message.Result.duration_ms result);
117117+ (match Message.Result.total_cost_usd result with
118118+ | Some cost -> Printf.printf "Cost: $%.4f\n" cost
119119+ | None -> ());
120120+ Printf.printf "Turns: %d\n" (Message.Result.num_turns result)
121121+ | _ -> ())
122122+ messages
118123119124let main () =
120125 log_setup ();
···143148 print_endline "\n\n### Example 3: Development Configuration ###";
144149 print_endline "Purpose: Development with user/project settings";
145150 let config = dev_config () in
146146- run_query ~sw process_mgr config "What is functional programming? One sentence.";
151151+ run_query ~sw process_mgr config
152152+ "What is functional programming? One sentence.";
147153148154 (* Example: Test configuration *)
149155 print_endline "\n\n### Example 4: Test Configuration ###";
···156162 print_endline "=============================================="
157163158164let () =
159159- try
160160- main ()
161161- with
162162- | e ->
163163- Printf.eprintf "Error: %s\n" (Printexc.to_string e);
164164- Printexc.print_backtrace stderr;
165165- exit 1
165165+ try main ()
166166+ with e ->
167167+ Printf.eprintf "Error: %s\n" (Printexc.to_string e);
168168+ Printexc.print_backtrace stderr;
169169+ exit 1
+94-79
test/camel_jokes.ml
···11open Eio.Std
2233let src = Logs.Src.create "camel_jokes" ~doc:"Camel joke competition"
44+45module Log = (val Logs.src_log src : Logs.LOG)
5667let process_claude_response client name =
78 Log.info (fun m -> m "=== %s's Response ===" name);
89 let messages = Claude.Client.receive_all client in
99- List.iter (fun msg ->
1010- match msg with
1111- | Claude.Message.Assistant msg ->
1212- List.iter (function
1313- | Claude.Content_block.Text t ->
1414- let text = Claude.Content_block.Text.text t in
1515- Log.app (fun m -> m "%s: %s" name text)
1616- | Claude.Content_block.Tool_use t ->
1717- Log.debug (fun m -> m "%s using tool: %s" name
1818- (Claude.Content_block.Tool_use.name t))
1919- | Claude.Content_block.Thinking t ->
2020- Log.debug (fun m -> m "%s thinking: %s" name
2121- (Claude.Content_block.Thinking.thinking t))
2222- | _ -> ()
2323- ) (Claude.Message.Assistant.content msg);
2424- Log.debug (fun m -> m "%s using model: %s" name
2525- (Claude.Message.Assistant.model msg))
2626- | Claude.Message.Result msg ->
2727- if Claude.Message.Result.is_error msg then
2828- Log.err (fun m -> m "Error from %s!" name)
2929- else
3030- (match Claude.Message.Result.total_cost_usd msg with
3131- | Some cost ->
3232- Log.info (fun m -> m "%s's joke cost: $%.6f" name cost)
3333- | None -> ());
3434- Log.debug (fun m -> m "%s session: %s, duration: %dms"
3535- name
3636- (Claude.Message.Result.session_id msg)
3737- (Claude.Message.Result.duration_ms msg))
3838- | Claude.Message.System _ ->
3939- (* System messages are already logged by the library *)
4040- ()
4141- | Claude.Message.User _ ->
4242- (* User messages are already logged by the library *)
4343- ()
4444- ) messages
1010+ List.iter
1111+ (fun msg ->
1212+ match msg with
1313+ | Claude.Message.Assistant msg ->
1414+ List.iter
1515+ (function
1616+ | Claude.Content_block.Text t ->
1717+ let text = Claude.Content_block.Text.text t in
1818+ Log.app (fun m -> m "%s: %s" name text)
1919+ | Claude.Content_block.Tool_use t ->
2020+ Log.debug (fun m ->
2121+ m "%s using tool: %s" name
2222+ (Claude.Content_block.Tool_use.name t))
2323+ | Claude.Content_block.Thinking t ->
2424+ Log.debug (fun m ->
2525+ m "%s thinking: %s" name
2626+ (Claude.Content_block.Thinking.thinking t))
2727+ | _ -> ())
2828+ (Claude.Message.Assistant.content msg);
2929+ Log.debug (fun m ->
3030+ m "%s using model: %s" name (Claude.Message.Assistant.model msg))
3131+ | Claude.Message.Result msg ->
3232+ (if Claude.Message.Result.is_error msg then
3333+ Log.err (fun m -> m "Error from %s!" name)
3434+ else
3535+ match Claude.Message.Result.total_cost_usd msg with
3636+ | Some cost ->
3737+ Log.info (fun m -> m "%s's joke cost: $%.6f" name cost)
3838+ | None -> ());
3939+ Log.debug (fun m ->
4040+ m "%s session: %s, duration: %dms" name
4141+ (Claude.Message.Result.session_id msg)
4242+ (Claude.Message.Result.duration_ms msg))
4343+ | Claude.Message.System _ ->
4444+ (* System messages are already logged by the library *)
4545+ ()
4646+ | Claude.Message.User _ ->
4747+ (* User messages are already logged by the library *)
4848+ ())
4949+ messages
45504651let run_claude ~sw ~env name prompt =
4752 Log.info (fun m -> m "🐪 Starting %s..." name);
4848- let options = Claude.Options.create ~model:(Claude.Model.of_string "sonnet") ~allowed_tools:[] () in
4949-5050- let client = Claude.Client.create ~options ~sw
5151- ~process_mgr:env#process_mgr
5252- () in
5353-5353+ let options =
5454+ Claude.Options.create
5555+ ~model:(Claude.Model.of_string "sonnet")
5656+ ~allowed_tools:[] ()
5757+ in
5858+5959+ let client =
6060+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ()
6161+ in
6262+5463 Claude.Client.query client prompt;
5564 process_claude_response client name
56655766let main ~env =
5867 Switch.run @@ fun sw ->
5959-6068 Log.app (fun m -> m "🐪 Starting the Great Camel Joke Competition! 🐪");
6169 Log.app (fun m -> m "================================================\n");
6262-6363- let prompts = [
6464- "Claude 1", "Tell me a short, funny joke about camels! Make it original and clever.";
6565- "Claude 2", "Give me your best camel joke - something witty and unexpected!";
6666- "Claude 3", "Share a hilarious camel joke that will make everyone laugh!";
6767- ] in
6868-7070+7171+ let prompts =
7272+ [
7373+ ( "Claude 1",
7474+ "Tell me a short, funny joke about camels! Make it original and clever."
7575+ );
7676+ ( "Claude 2",
7777+ "Give me your best camel joke - something witty and unexpected!" );
7878+ ("Claude 3", "Share a hilarious camel joke that will make everyone laugh!");
7979+ ]
8080+ in
8181+6982 (* Run all three Claudes concurrently *)
7070- Fiber.all (
7171- List.map (fun (name, prompt) ->
7272- fun () -> run_claude ~sw ~env name prompt
7373- ) prompts
7474- );
7575-8383+ Fiber.all
8484+ (List.map
8585+ (fun (name, prompt) -> fun () -> run_claude ~sw ~env name prompt)
8686+ prompts);
8787+7688 Log.app (fun m -> m "\n================================================");
7789 Log.app (fun m -> m "🎉 The Camel Joke Competition is complete! 🎉")
7890···9811099111let cmd env =
100112 let doc = "Run the Great Camel Joke Competition using Claude" in
101101- let man = [
102102- `S Manpage.s_description;
103103- `P "This program runs three concurrent Claude instances to generate camel jokes.";
104104- `P "Use $(b,-v) or $(b,--verbosity=info) to see RPC message traffic.";
105105- `P "Use $(b,-vv) or $(b,--verbosity=debug) to see all internal operations.";
106106- `S Manpage.s_examples;
107107- `P "Run with normal output:";
108108- `Pre " $(mname)";
109109- `P "Run with info-level logging (RPC traffic):";
110110- `Pre " $(mname) -v";
111111- `Pre " $(mname) --verbosity=info";
112112- `P "Run with debug logging (all operations):";
113113- `Pre " $(mname) -vv";
114114- `Pre " $(mname) --verbosity=debug";
115115- `P "Enable debug for specific modules:";
116116- `Pre " LOGS='claude.transport=debug' $(mname)";
117117- `Pre " LOGS='claude.message=info,camel_jokes=debug' $(mname)";
118118- `S Manpage.s_bugs;
119119- `P "Report bugs at https://github.com/your-repo/issues";
120120- ] in
113113+ let man =
114114+ [
115115+ `S Manpage.s_description;
116116+ `P
117117+ "This program runs three concurrent Claude instances to generate camel \
118118+ jokes.";
119119+ `P "Use $(b,-v) or $(b,--verbosity=info) to see RPC message traffic.";
120120+ `P
121121+ "Use $(b,-vv) or $(b,--verbosity=debug) to see all internal operations.";
122122+ `S Manpage.s_examples;
123123+ `P "Run with normal output:";
124124+ `Pre " $(mname)";
125125+ `P "Run with info-level logging (RPC traffic):";
126126+ `Pre " $(mname) -v";
127127+ `Pre " $(mname) --verbosity=info";
128128+ `P "Run with debug logging (all operations):";
129129+ `Pre " $(mname) -vv";
130130+ `Pre " $(mname) --verbosity=debug";
131131+ `P "Enable debug for specific modules:";
132132+ `Pre " LOGS='claude.transport=debug' $(mname)";
133133+ `Pre " LOGS='claude.message=info,camel_jokes=debug' $(mname)";
134134+ `S Manpage.s_bugs;
135135+ `P "Report bugs at https://github.com/your-repo/issues";
136136+ ]
137137+ in
121138 let info = Cmd.info "camel_jokes" ~version:"1.0" ~doc ~man in
122139 Cmd.v info (main_term env)
123140124124-let () =
125125- Eio_main.run @@ fun env ->
126126- exit (Cmd.eval (cmd env))
141141+let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))
+67-52
test/discovery_demo.ml
···11open Eio.Std
2233-let src = Logs.Src.create "discovery_demo" ~doc:"Permission discovery demonstration"
33+let src =
44+ Logs.Src.create "discovery_demo" ~doc:"Permission discovery demonstration"
55+46module Log = (val Logs.src_log src : Logs.LOG)
5768let process_response client =
79 let messages = Claude.Client.receive_all client in
88- List.iter (fun msg ->
99- match msg with
1010- | Claude.Message.Assistant msg ->
1111- List.iter (function
1212- | Claude.Content_block.Text t ->
1313- let text = Claude.Content_block.Text.text t in
1414- Log.app (fun m -> m "Claude: %s"
1515- (if String.length text > 100 then
1616- String.sub text 0 100 ^ "..."
1717- else text))
1818- | Claude.Content_block.Tool_use t ->
1919- Log.info (fun m -> m "Tool use: %s"
2020- (Claude.Content_block.Tool_use.name t))
2121- | _ -> ()
2222- ) (Claude.Message.Assistant.content msg)
2323- | Claude.Message.Result msg ->
2424- if Claude.Message.Result.is_error msg then
2525- Log.err (fun m -> m "Error occurred!")
2626- else
2727- (match Claude.Message.Result.total_cost_usd msg with
2828- | Some cost ->
2929- Log.info (fun m -> m "Cost: $%.6f" cost)
3030- | None -> ())
3131- | _ -> ()
3232- ) messages
1010+ List.iter
1111+ (fun msg ->
1212+ match msg with
1313+ | Claude.Message.Assistant msg ->
1414+ List.iter
1515+ (function
1616+ | Claude.Content_block.Text t ->
1717+ let text = Claude.Content_block.Text.text t in
1818+ Log.app (fun m ->
1919+ m "Claude: %s"
2020+ (if String.length text > 100 then
2121+ String.sub text 0 100 ^ "..."
2222+ else text))
2323+ | Claude.Content_block.Tool_use t ->
2424+ Log.info (fun m ->
2525+ m "Tool use: %s" (Claude.Content_block.Tool_use.name t))
2626+ | _ -> ())
2727+ (Claude.Message.Assistant.content msg)
2828+ | Claude.Message.Result msg -> (
2929+ if Claude.Message.Result.is_error msg then
3030+ Log.err (fun m -> m "Error occurred!")
3131+ else
3232+ match Claude.Message.Result.total_cost_usd msg with
3333+ | Some cost -> Log.info (fun m -> m "Cost: $%.6f" cost)
3434+ | None -> ())
3535+ | _ -> ())
3636+ messages
33373438let run_discovery ~sw ~env =
3539 Log.app (fun m -> m "🔍 Permission Discovery Demo");
3640 Log.app (fun m -> m "=============================");
3741 Log.app (fun m -> m "This will discover what permissions Claude needs.\n");
3838-4242+3943 (* Create client with discovery mode *)
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-4444+ let options =
4545+ Claude.Options.create ~model:(Claude.Model.of_string "sonnet") ()
4646+ in
4747+ let client =
4848+ Claude.Client.discover_permissions
4949+ (Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ())
5050+ in
5151+4452 (* Send a prompt that will need permissions *)
4553 Log.app (fun m -> m "Asking Claude to read a secret file...");
4646- Claude.Client.query client
4747- "Please read the file test/secret_data.txt and tell me what the secret code is.";
5454+ Claude.Client.query client
5555+ "Please read the file test/secret_data.txt and tell me what the secret \
5656+ code is.";
4857 process_response client;
4949-5858+5059 (* Check what permissions were requested *)
5160 let permissions = Claude.Client.get_discovered_permissions client in
5261 if permissions = [] then
5353- Log.app (fun m -> m "\n📋 No permissions were requested (Claude may have used its knowledge).")
6262+ Log.app (fun m ->
6363+ m
6464+ "\n\
6565+ 📋 No permissions were requested (Claude may have used its \
6666+ knowledge).")
5467 else begin
5568 Log.app (fun m -> m "\n📋 Permissions that were requested:");
5656- List.iter (fun rule ->
5757- Log.app (fun m -> m " - Tool: %s%s"
5858- (Claude.Permissions.Rule.tool_name rule)
5959- (match Claude.Permissions.Rule.rule_content rule with
6060- | Some content -> Printf.sprintf " (rule: %s)" content
6161- | None -> ""))
6262- ) permissions
6969+ List.iter
7070+ (fun rule ->
7171+ Log.app (fun m ->
7272+ m " - Tool: %s%s"
7373+ (Claude.Permissions.Rule.tool_name rule)
7474+ (match Claude.Permissions.Rule.rule_content rule with
7575+ | Some content -> Printf.sprintf " (rule: %s)" content
7676+ | None -> "")))
7777+ permissions
6378 end
64796565-let main ~env =
6666- Switch.run @@ fun sw ->
6767- run_discovery ~sw ~env
8080+let main ~env = Switch.run @@ fun sw -> run_discovery ~sw ~env
68816982(* Command-line interface *)
7083open Cmdliner
···84978598let cmd env =
8699 let doc = "Discover what permissions Claude needs" in
8787- let man = [
8888- `S Manpage.s_description;
8989- `P "This program runs Claude in discovery mode to see what permissions it requests.";
9090- ] in
100100+ let man =
101101+ [
102102+ `S Manpage.s_description;
103103+ `P
104104+ "This program runs Claude in discovery mode to see what permissions it \
105105+ requests.";
106106+ ]
107107+ in
91108 let info = Cmd.info "discovery_demo" ~version:"1.0" ~doc ~man in
92109 Cmd.v info (main_term env)
931109494-let () =
9595- Eio_main.run @@ fun env ->
9696- exit (Cmd.eval (cmd env))111111+let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))
···20202121 (* Consume initial messages *)
2222 let messages = Client.receive_all client in
2323- List.iter (function
2424- | Message.Assistant msg ->
2525- List.iter (function
2626- | Content_block.Text t ->
2727- traceln "Assistant: %s" (Content_block.Text.text t)
2828- | _ -> ()
2929- ) (Message.Assistant.content msg)
3030- | _ -> ()
3131- ) messages;
2323+ List.iter
2424+ (function
2525+ | Message.Assistant msg ->
2626+ List.iter
2727+ (function
2828+ | Content_block.Text t ->
2929+ traceln "Assistant: %s" (Content_block.Text.text t)
3030+ | _ -> ())
3131+ (Message.Assistant.content msg)
3232+ | _ -> ())
3333+ messages;
32343335 traceln "\n2. Getting server info...";
3436 (try
3535- let info = Client.get_server_info client in
3636- traceln "Server version: %s" (Sdk_control.Server_info.version info);
3737- traceln "Capabilities: [%s]"
3838- (String.concat ", " (Sdk_control.Server_info.capabilities info));
3939- traceln "Commands: [%s]"
4040- (String.concat ", " (Sdk_control.Server_info.commands info));
4141- traceln "Output styles: [%s]"
4242- (String.concat ", " (Sdk_control.Server_info.output_styles info));
4343- with
3737+ let info = Client.get_server_info client in
3838+ traceln "Server version: %s" (Sdk_control.Server_info.version info);
3939+ traceln "Capabilities: [%s]"
4040+ (String.concat ", " (Sdk_control.Server_info.capabilities info));
4141+ traceln "Commands: [%s]"
4242+ (String.concat ", " (Sdk_control.Server_info.commands info));
4343+ traceln "Output styles: [%s]"
4444+ (String.concat ", " (Sdk_control.Server_info.output_styles info))
4545+ with
4446 | Failure msg -> traceln "Failed to get server info: %s" msg
4547 | exn -> traceln "Error getting server info: %s" (Printexc.to_string exn));
46484749 traceln "\n3. Switching to a different model (if available)...";
4850 (try
4949- Client.set_model_string client "claude-sonnet-4";
5050- traceln "Model switched successfully";
5151+ Client.set_model client (Model.of_string "claude-sonnet-4");
5252+ traceln "Model switched successfully";
51535252- (* Query with new model *)
5353- Client.query client "Confirm your model again please.";
5454- let messages = Client.receive_all client in
5555- List.iter (function
5656- | Message.Assistant msg ->
5757- List.iter (function
5858- | Content_block.Text t ->
5959- traceln "Assistant (new model): %s" (Content_block.Text.text t)
6060- | _ -> ()
6161- ) (Message.Assistant.content msg)
6262- | _ -> ()
6363- ) messages;
6464- with
5454+ (* Query with new model *)
5555+ Client.query client "Confirm your model again please.";
5656+ let messages = Client.receive_all client in
5757+ List.iter
5858+ (function
5959+ | Message.Assistant msg ->
6060+ List.iter
6161+ (function
6262+ | Content_block.Text t ->
6363+ traceln "Assistant (new model): %s"
6464+ (Content_block.Text.text t)
6565+ | _ -> ())
6666+ (Message.Assistant.content msg)
6767+ | _ -> ())
6868+ messages
6969+ with
6570 | Failure msg -> traceln "Failed to switch model: %s" msg
6671 | exn -> traceln "Error switching model: %s" (Printexc.to_string exn));
67726873 traceln "\n4. Changing permission mode...";
6974 (try
7070- Client.set_permission_mode client Permissions.Mode.Accept_edits;
7171- traceln "Permission mode changed to Accept_edits";
7272- with
7575+ Client.set_permission_mode client Permissions.Mode.Accept_edits;
7676+ traceln "Permission mode changed to Accept_edits"
7777+ with
7378 | Failure msg -> traceln "Failed to change permission mode: %s" msg
7479 | exn -> traceln "Error changing permission mode: %s" (Printexc.to_string exn));
7580···78837984let () =
8085 Eio_main.run @@ fun env ->
8181- try
8282- run env
8383- with
8686+ try run env with
8487 | Transport.CLI_not_found msg ->
8588 traceln "Error: %s" msg;
8689 traceln "Make sure the 'claude' CLI is installed and authenticated.";
+62-60
test/hooks_example.ml
···11open Eio.Std
2233let src = Logs.Src.create "hooks_example" ~doc:"Hooks example"
44+45module Log = (val Logs.src_log src : Logs.LOG)
5667(* Example 1: Block dangerous bash commands *)
···1415 | Some command ->
1516 if String.length command >= 6 && String.sub command 0 6 = "rm -rf" then begin
1617 Log.app (fun m -> m "🚫 Blocked dangerous command: %s" command);
1717- let output = Claude.Hooks.PreToolUse.deny
1818- ~reason:"Command contains dangerous 'rm -rf' pattern" () in
1818+ let output =
1919+ Claude.Hooks.PreToolUse.deny
2020+ ~reason:"Command contains dangerous 'rm -rf' pattern" ()
2121+ in
1922 Claude.Hooks.continue
2023 ~system_message:"Blocked dangerous rm -rf command"
2121- ~hook_specific_output:(Claude.Hooks.PreToolUse.output_to_json output)
2424+ ~hook_specific_output:
2525+ (Claude.Hooks.PreToolUse.output_to_json output)
2226 ()
2323- end else
2424- Claude.Hooks.continue ()
2525- | _ ->
2626- Claude.Hooks.continue ()
2727- else
2828- Claude.Hooks.continue ()
2727+ end
2828+ else Claude.Hooks.continue ()
2929+ | _ -> Claude.Hooks.continue ()
3030+ else Claude.Hooks.continue ()
29313032(* Example 2: Log all tool usage *)
3133let log_tool_usage ~input ~tool_use_id ~context:_ =
···4244 (* Configure hooks *)
4345 let hooks =
4446 Claude.Hooks.empty
4545- |> Claude.Hooks.add Claude.Hooks.Pre_tool_use [
4646- (* Log all tool usage *)
4747- Claude.Hooks.matcher [log_tool_usage];
4848- (* Block dangerous bash commands *)
4949- Claude.Hooks.matcher ~pattern:"Bash" [block_dangerous_bash];
5050- ]
4747+ |> Claude.Hooks.add Claude.Hooks.Pre_tool_use
4848+ [
4949+ (* Log all tool usage *)
5050+ Claude.Hooks.matcher [ log_tool_usage ];
5151+ (* Block dangerous bash commands *)
5252+ Claude.Hooks.matcher ~pattern:"Bash" [ block_dangerous_bash ];
5353+ ]
5154 in
52555353- let options = Claude.Options.create
5454- ~model:(Claude.Model.of_string "sonnet")
5555- ~hooks
5656- () in
5656+ let options =
5757+ Claude.Options.create ~model:(Claude.Model.of_string "sonnet") ~hooks ()
5858+ in
57595858- let client = Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr () in
6060+ let client =
6161+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ()
6262+ in
59636064 (* Test 1: Safe command (should work) *)
6165 Log.app (fun m -> m "Test 1: Safe bash command");
6266 Claude.Client.query client "Run the bash command: echo 'Hello from hooks!'";
63676468 let messages = Claude.Client.receive_all client in
6565- List.iter (fun msg ->
6666- match msg with
6767- | Claude.Message.Assistant msg ->
6868- List.iter (function
6969- | Claude.Content_block.Text t ->
7070- let text = Claude.Content_block.Text.text t in
7171- if String.length text > 0 then
7272- Log.app (fun m -> m "Claude: %s" text)
7373- | _ -> ()
7474- ) (Claude.Message.Assistant.content msg)
7575- | Claude.Message.Result msg ->
7676- if Claude.Message.Result.is_error msg then
7777- Log.err (fun m -> m "❌ Error!")
7878- else
7979- Log.app (fun m -> m "✅ Test 1 complete\n")
8080- | _ -> ()
8181- ) messages;
6969+ List.iter
7070+ (fun msg ->
7171+ match msg with
7272+ | Claude.Message.Assistant msg ->
7373+ List.iter
7474+ (function
7575+ | Claude.Content_block.Text t ->
7676+ let text = Claude.Content_block.Text.text t in
7777+ if String.length text > 0 then
7878+ Log.app (fun m -> m "Claude: %s" text)
7979+ | _ -> ())
8080+ (Claude.Message.Assistant.content msg)
8181+ | Claude.Message.Result msg ->
8282+ if Claude.Message.Result.is_error msg then
8383+ Log.err (fun m -> m "❌ Error!")
8484+ else Log.app (fun m -> m "✅ Test 1 complete\n")
8585+ | _ -> ())
8686+ messages;
82878388 (* Test 2: Dangerous command (should be blocked) *)
8489 Log.app (fun m -> m "Test 2: Dangerous bash command (should be blocked)");
8590 Claude.Client.query client "Run the bash command: rm -rf /tmp/test";
86918792 let messages = Claude.Client.receive_all client in
8888- List.iter (fun msg ->
8989- match msg with
9090- | Claude.Message.Assistant msg ->
9191- List.iter (function
9292- | Claude.Content_block.Text t ->
9393- let text = Claude.Content_block.Text.text t in
9494- if String.length text > 0 then
9595- Log.app (fun m -> m "Claude: %s" text)
9696- | _ -> ()
9797- ) (Claude.Message.Assistant.content msg)
9898- | Claude.Message.Result msg ->
9999- if Claude.Message.Result.is_error msg then
100100- Log.err (fun m -> m "❌ Error!")
101101- else
102102- Log.app (fun m -> m "✅ Test 2 complete")
103103- | _ -> ()
104104- ) messages;
9393+ List.iter
9494+ (fun msg ->
9595+ match msg with
9696+ | Claude.Message.Assistant msg ->
9797+ List.iter
9898+ (function
9999+ | Claude.Content_block.Text t ->
100100+ let text = Claude.Content_block.Text.text t in
101101+ if String.length text > 0 then
102102+ Log.app (fun m -> m "Claude: %s" text)
103103+ | _ -> ())
104104+ (Claude.Message.Assistant.content msg)
105105+ | Claude.Message.Result msg ->
106106+ if Claude.Message.Result.is_error msg then
107107+ Log.err (fun m -> m "❌ Error!")
108108+ else Log.app (fun m -> m "✅ Test 2 complete")
109109+ | _ -> ())
110110+ messages;
105111106112 Log.app (fun m -> m "\n====================");
107113 Log.app (fun m -> m "✨ Example complete!")
108114109109-let main ~env =
110110- Switch.run @@ fun sw ->
111111- run_example ~sw ~env
115115+let main ~env = Switch.run @@ fun sw -> run_example ~sw ~env
112116113117(* Command-line interface *)
114118open Cmdliner
···135139 let info = Cmd.info "hooks_example" ~version:"1.0" ~doc in
136140 Cmd.v info (main_term env)
137141138138-let () =
139139- Eio_main.run @@ fun env ->
140140- exit (Cmd.eval (cmd env))
142142+let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))
+128-115
test/permission_demo.ml
···11open Eio.Std
2233-let src = Logs.Src.create "permission_demo" ~doc:"Permission callback demonstration"
33+let src =
44+ Logs.Src.create "permission_demo" ~doc:"Permission callback demonstration"
55+46module Log = (val Logs.src_log src : Logs.LOG)
5768(* Mutable state to track what permissions have been granted *)
79module Granted = struct
88- module StringSet = Set.Make(String)
1010+ module StringSet = Set.Make (String)
1111+912 let tools = ref StringSet.empty
1010-1313+1114 let grant tool_name =
1215 tools := StringSet.add tool_name !tools;
1316 Log.app (fun m -> m "✅ Permission granted for: %s" tool_name)
1414-1717+1518 let deny tool_name =
1619 Log.app (fun m -> m "❌ Permission denied for: %s" tool_name)
1717-1818- let is_granted tool_name =
1919- StringSet.mem tool_name !tools
2020-2020+2121+ let is_granted tool_name = StringSet.mem tool_name !tools
2222+2123 let list () =
2224 if StringSet.is_empty !tools then
2325 Log.app (fun m -> m "No permissions granted yet")
2426 else
2525- Log.app (fun m -> m "Currently granted permissions: %s"
2626- (StringSet.elements !tools |> String.concat ", "))
2727+ Log.app (fun m ->
2828+ m "Currently granted permissions: %s"
2929+ (StringSet.elements !tools |> String.concat ", "))
2730end
28312932(* Interactive permission callback *)
···3134 Log.info (fun m -> m "🔔 Permission callback invoked for tool: %s" tool_name);
3235 Log.app (fun m -> m "\n🔐 PERMISSION REQUEST 🔐");
3336 Log.app (fun m -> m "Tool: %s" tool_name);
3434-3737+3538 (* Log the full input for debugging *)
3639 Log.info (fun m -> m "Full input JSON: %s" (Test_json_utils.to_string input));
3737-4040+3841 (* Show input details *)
3942 (* Try to extract key information from the input *)
4043 (try
4141- match tool_name with
4242- | "Read" ->
4343- (match Test_json_utils.get_string input "file_path" with
4444- | Some file_path -> Log.app (fun m -> m "File: %s" file_path)
4545- | None -> ())
4646- | "Bash" ->
4747- (match Test_json_utils.get_string input "command" with
4848- | Some command -> Log.app (fun m -> m "Command: %s" command)
4949- | None -> ())
5050- | "Write" | "Edit" ->
5151- (match Test_json_utils.get_string input "file_path" with
5252- | Some file_path -> Log.app (fun m -> m "File: %s" file_path)
5353- | None -> ())
5454- | "Glob" ->
5555- (match Test_json_utils.get_string input "pattern" with
5656- | Some pattern ->
5757- Log.app (fun m -> m "Pattern: %s" pattern);
5858- (match Test_json_utils.get_string input "path" with
5959- | Some path -> Log.app (fun m -> m "Path: %s" path)
6060- | None -> Log.app (fun m -> m "Path: (current directory)"))
6161- | None -> ())
6262- | "Grep" ->
6363- (match Test_json_utils.get_string input "pattern" with
6464- | Some pattern ->
6565- Log.app (fun m -> m "Pattern: %s" pattern);
6666- (match Test_json_utils.get_string input "path" with
6767- | Some path -> Log.app (fun m -> m "Path: %s" path)
6868- | None -> Log.app (fun m -> m "Path: (current directory)"))
6969- | None -> ())
7070- | _ ->
7171- Log.app (fun m -> m "Input: %s" (Test_json_utils.to_string input))
7272- with exn ->
7373- Log.info (fun m -> m "Failed to parse input details: %s" (Printexc.to_string exn)));
7474-4444+ match tool_name with
4545+ | "Read" -> (
4646+ match Test_json_utils.get_string input "file_path" with
4747+ | Some file_path -> Log.app (fun m -> m "File: %s" file_path)
4848+ | None -> ())
4949+ | "Bash" -> (
5050+ match Test_json_utils.get_string input "command" with
5151+ | Some command -> Log.app (fun m -> m "Command: %s" command)
5252+ | None -> ())
5353+ | "Write" | "Edit" -> (
5454+ match Test_json_utils.get_string input "file_path" with
5555+ | Some file_path -> Log.app (fun m -> m "File: %s" file_path)
5656+ | None -> ())
5757+ | "Glob" -> (
5858+ match Test_json_utils.get_string input "pattern" with
5959+ | Some pattern -> (
6060+ Log.app (fun m -> m "Pattern: %s" pattern);
6161+ match Test_json_utils.get_string input "path" with
6262+ | Some path -> Log.app (fun m -> m "Path: %s" path)
6363+ | None -> Log.app (fun m -> m "Path: (current directory)"))
6464+ | None -> ())
6565+ | "Grep" -> (
6666+ match Test_json_utils.get_string input "pattern" with
6767+ | Some pattern -> (
6868+ Log.app (fun m -> m "Pattern: %s" pattern);
6969+ match Test_json_utils.get_string input "path" with
7070+ | Some path -> Log.app (fun m -> m "Path: %s" path)
7171+ | None -> Log.app (fun m -> m "Path: (current directory)"))
7272+ | None -> ())
7373+ | _ -> Log.app (fun m -> m "Input: %s" (Test_json_utils.to_string input))
7474+ with exn ->
7575+ Log.info (fun m ->
7676+ m "Failed to parse input details: %s" (Printexc.to_string exn)));
7777+7578 (* Check if already granted *)
7679 if Granted.is_granted tool_name then begin
7780 Log.app (fun m -> m "→ Auto-approved (previously granted)");
7881 Log.info (fun m -> m "Returning allow result for %s" tool_name);
7982 Claude.Permissions.Result.allow ()
8080- end else begin
8383+ end
8484+ else begin
8185 (* Ask user - read from /dev/tty since stdin is connected to Claude process *)
8286 Printf.printf "Allow? [y/N/always]: %!";
8387 let tty = open_in "/dev/tty" in
···9094 Claude.Permissions.Result.allow ()
9195 | "a" | "always" ->
9296 Granted.grant tool_name;
9393- Log.info (fun m -> m "User granted permanent permission for %s" tool_name);
9797+ Log.info (fun m ->
9898+ m "User granted permanent permission for %s" tool_name);
9499 Claude.Permissions.Result.allow ()
95100 | _ ->
96101 Granted.deny tool_name;
97102 Log.info (fun m -> m "User denied permission for %s" tool_name);
9898- Claude.Permissions.Result.deny ~message:(Printf.sprintf "User denied access to %s" tool_name) ~interrupt:false ()
103103+ Claude.Permissions.Result.deny
104104+ ~message:(Printf.sprintf "User denied access to %s" tool_name)
105105+ ~interrupt:false ()
99106 end
100107101108let process_response client =
102109 let messages = Claude.Client.receive_all client in
103103- List.iter (fun msg ->
104104- match msg with
105105- | Claude.Message.Assistant msg ->
106106- List.iter (function
107107- | Claude.Content_block.Text t ->
108108- let text = Claude.Content_block.Text.text t in
109109- Log.app (fun m -> m "\n📝 Claude says:\n%s" text)
110110- | Claude.Content_block.Tool_use t ->
111111- Log.info (fun m -> m "🔧 Tool use: %s (id: %s)"
112112- (Claude.Content_block.Tool_use.name t)
113113- (Claude.Content_block.Tool_use.id t))
114114- | _ -> ()
115115- ) (Claude.Message.Assistant.content msg)
116116- | Claude.Message.Result msg ->
117117- if Claude.Message.Result.is_error msg then
118118- Log.err (fun m -> m "❌ Error occurred!")
119119- else
120120- (match Claude.Message.Result.total_cost_usd msg with
121121- | Some cost ->
122122- Log.info (fun m -> m "💰 Cost: $%.6f" cost)
123123- | None -> ());
124124- Log.info (fun m -> m "⏱️ Duration: %dms"
125125- (Claude.Message.Result.duration_ms msg))
126126- | _ -> ()
127127- ) messages
110110+ List.iter
111111+ (fun msg ->
112112+ match msg with
113113+ | Claude.Message.Assistant msg ->
114114+ List.iter
115115+ (function
116116+ | Claude.Content_block.Text t ->
117117+ let text = Claude.Content_block.Text.text t in
118118+ Log.app (fun m -> m "\n📝 Claude says:\n%s" text)
119119+ | Claude.Content_block.Tool_use t ->
120120+ Log.info (fun m ->
121121+ m "🔧 Tool use: %s (id: %s)"
122122+ (Claude.Content_block.Tool_use.name t)
123123+ (Claude.Content_block.Tool_use.id t))
124124+ | _ -> ())
125125+ (Claude.Message.Assistant.content msg)
126126+ | Claude.Message.Result msg ->
127127+ (if Claude.Message.Result.is_error msg then
128128+ Log.err (fun m -> m "❌ Error occurred!")
129129+ else
130130+ match Claude.Message.Result.total_cost_usd msg with
131131+ | Some cost -> Log.info (fun m -> m "💰 Cost: $%.6f" cost)
132132+ | None -> ());
133133+ Log.info (fun m ->
134134+ m "⏱️ Duration: %dms" (Claude.Message.Result.duration_ms msg))
135135+ | _ -> ())
136136+ messages
128137129138let run_demo ~sw ~env =
130139 Log.app (fun m -> m "🚀 Starting Permission Demo");
131140 Log.app (fun m -> m "==================================");
132141 Log.app (fun m -> m "This demo starts with NO permissions.");
133142 Log.app (fun m -> m "Claude will request permissions as needed.\n");
134134-143143+135144 (* Create options with custom permission callback *)
136145 (* DON'T specify allowed_tools - let the permission callback handle everything.
137146 The Default permission mode with a callback should send requests for all tools. *)
138138- let options = Claude.Options.create
139139- ~model:(Claude.Model.of_string "sonnet")
140140- ~permission_mode:Claude.Permissions.Mode.Default
141141- ~permission_callback:interactive_permission_callback
142142- () in
143143-144144- let client = Claude.Client.create ~options ~sw
145145- ~process_mgr:env#process_mgr
146146- () in
147147-147147+ let options =
148148+ Claude.Options.create
149149+ ~model:(Claude.Model.of_string "sonnet")
150150+ ~permission_mode:Claude.Permissions.Mode.Default
151151+ ~permission_callback:interactive_permission_callback ()
152152+ in
153153+154154+ let client =
155155+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ()
156156+ in
157157+148158 (* First prompt - Claude will need to request Read permission for ../lib *)
149159 Log.app (fun m -> m "\n📤 Sending first prompt (reading from ../lib)...");
150150- Claude.Client.query client
151151- "Please read and analyze the source files in the ../lib directory. Focus on the main OCaml modules and their purpose. What is the overall architecture of this Claude library?";
160160+ Claude.Client.query client
161161+ "Please read and analyze the source files in the ../lib directory. Focus \
162162+ on the main OCaml modules and their purpose. What is the overall \
163163+ architecture of this Claude library?";
152164 process_response client;
153153-165165+154166 (* Show current permissions *)
155167 Log.app (fun m -> m "\n📋 Current permission status:");
156168 Granted.list ();
157157-169169+158170 (* Second prompt - will need Write permission *)
159171 Log.app (fun m -> m "\n📤 Sending second prompt (writing TEST.md)...");
160160- Claude.Client.query client
161161- "Now write a summary of what you learned about the Claude library architecture to a file called TEST.md in the current directory. Include the main modules, their purposes, and how they work together.";
172172+ Claude.Client.query client
173173+ "Now write a summary of what you learned about the Claude library \
174174+ architecture to a file called TEST.md in the current directory. Include \
175175+ the main modules, their purposes, and how they work together.";
162176 process_response client;
163163-177177+164178 (* Show final permissions *)
165179 Log.app (fun m -> m "\n📋 Final permission status:");
166180 Granted.list ();
167167-181181+168182 Log.app (fun m -> m "\n==================================");
169183 Log.app (fun m -> m "✨ Demo complete!")
170184171171-let main ~env =
172172- Switch.run @@ fun sw ->
173173- run_demo ~sw ~env
185185+let main ~env = Switch.run @@ fun sw -> run_demo ~sw ~env
174186175187(* Command-line interface *)
176188open Cmdliner
···196208197209let cmd env =
198210 let doc = "Demonstrate Claude's dynamic permission system" in
199199- let man = [
200200- `S Manpage.s_description;
201201- `P "This program demonstrates how to use permission callbacks with Claude.";
202202- `P "It starts with no permissions and asks for them interactively.";
203203- `P "You can grant permissions for:";
204204- `P "- Individual requests (y/yes)";
205205- `P "- All future requests of that type (a/always)";
206206- `P "- Or deny the request (n/no or just press Enter)";
207207- `S Manpage.s_examples;
208208- `P "Run the demo:";
209209- `Pre " $(mname)";
210210- `P "Run with verbose output to see message flow:";
211211- `Pre " $(mname) -v";
212212- `S Manpage.s_bugs;
213213- `P "Report bugs at https://github.com/your-repo/issues";
214214- ] in
211211+ let man =
212212+ [
213213+ `S Manpage.s_description;
214214+ `P
215215+ "This program demonstrates how to use permission callbacks with Claude.";
216216+ `P "It starts with no permissions and asks for them interactively.";
217217+ `P "You can grant permissions for:";
218218+ `P "- Individual requests (y/yes)";
219219+ `P "- All future requests of that type (a/always)";
220220+ `P "- Or deny the request (n/no or just press Enter)";
221221+ `S Manpage.s_examples;
222222+ `P "Run the demo:";
223223+ `Pre " $(mname)";
224224+ `P "Run with verbose output to see message flow:";
225225+ `Pre " $(mname) -v";
226226+ `S Manpage.s_bugs;
227227+ `P "Report bugs at https://github.com/your-repo/issues";
228228+ ]
229229+ in
215230 let info = Cmd.info "permission_demo" ~version:"1.0" ~doc ~man in
216231 Cmd.v info (main_term env)
217232218218-let () =
219219- Eio_main.run @@ fun env ->
220220- exit (Cmd.eval (cmd env))233233+let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))
+67-60
test/simple_permission_test.ml
···11open Eio.Std
2233let src = Logs.Src.create "simple_permission_test" ~doc:"Simple permission test"
44+45module Log = (val Logs.src_log src : Logs.LOG)
5667(* Auto-allow callback that logs what it sees *)
···1617 Log.app (fun m -> m "====================================================");
17181819 (* Create options with permission callback *)
1919- let options = Claude.Options.create
2020- ~model:(Claude.Model.of_string "sonnet")
2121- ~permission_callback:auto_allow_callback
2222- () in
2020+ let options =
2121+ Claude.Options.create
2222+ ~model:(Claude.Model.of_string "sonnet")
2323+ ~permission_callback:auto_allow_callback ()
2424+ in
23252426 Log.app (fun m -> m "Creating client with permission callback...");
2525- let client = Claude.Client.create ~options ~sw
2626- ~process_mgr:env#process_mgr
2727- () in
2727+ let client =
2828+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ()
2929+ in
28302931 (* Query that should trigger Write tool *)
3032 Log.app (fun m -> m "\n📤 Asking Claude to write a file...");
···3840 let tool_count = ref 0 in
3941 let write_used = ref false in
40424141- List.iter (fun msg ->
4242- match msg with
4343- | Claude.Message.Assistant msg ->
4444- List.iter (function
4545- | Claude.Content_block.Text t ->
4646- let text = Claude.Content_block.Text.text t in
4747- if String.length text > 0 then
4848- Log.app (fun m -> m "\n💬 Claude: %s" text)
4949- | Claude.Content_block.Tool_use t ->
5050- incr tool_count;
5151- let tool_name = Claude.Content_block.Tool_use.name t in
5252- if tool_name = "Write" then write_used := true;
5353- Log.app (fun m -> m "🔧 Tool use #%d: %s" !tool_count tool_name)
5454- | _ -> ()
5555- ) (Claude.Message.Assistant.content msg)
5656- | Claude.Message.User msg ->
5757- (* Check for tool results which might have errors *)
5858- (match Claude.Message.User.content msg with
5959- | Claude.Message.User.Blocks blocks ->
6060- List.iter (function
6161- | Claude.Content_block.Tool_result r ->
6262- let tool_use_id = Claude.Content_block.Tool_result.tool_use_id r in
6363- let is_error = Claude.Content_block.Tool_result.is_error r |> Option.value ~default:false in
6464- if is_error then begin
6565- Log.app (fun m -> m "\n⚠️ Tool result error for %s:" tool_use_id);
6666- (match Claude.Content_block.Tool_result.content r with
6767- | Some s -> Log.app (fun m -> m " %s" s)
6868- | None -> ())
6969- end
7070- | _ -> ()
7171- ) blocks
7272- | _ -> ())
7373- | Claude.Message.Result msg ->
7474- if Claude.Message.Result.is_error msg then
7575- Log.err (fun m -> m "\n❌ Error occurred!")
7676- else
7777- Log.app (fun m -> m "\n✅ Success!");
7878- (match Claude.Message.Result.total_cost_usd msg with
7979- | Some cost -> Log.app (fun m -> m "💰 Cost: $%.6f" cost)
8080- | None -> ());
8181- Log.app (fun m -> m "⏱️ Duration: %dms"
8282- (Claude.Message.Result.duration_ms msg))
8383- | _ -> ()
8484- ) messages;
4343+ List.iter
4444+ (fun msg ->
4545+ match msg with
4646+ | Claude.Message.Assistant msg ->
4747+ List.iter
4848+ (function
4949+ | Claude.Content_block.Text t ->
5050+ let text = Claude.Content_block.Text.text t in
5151+ if String.length text > 0 then
5252+ Log.app (fun m -> m "\n💬 Claude: %s" text)
5353+ | Claude.Content_block.Tool_use t ->
5454+ incr tool_count;
5555+ let tool_name = Claude.Content_block.Tool_use.name t in
5656+ if tool_name = "Write" then write_used := true;
5757+ Log.app (fun m ->
5858+ m "🔧 Tool use #%d: %s" !tool_count tool_name)
5959+ | _ -> ())
6060+ (Claude.Message.Assistant.content msg)
6161+ | Claude.Message.User msg -> (
6262+ (* Check for tool results which might have errors *)
6363+ match Claude.Message.User.content msg with
6464+ | Claude.Message.User.Blocks blocks ->
6565+ List.iter
6666+ (function
6767+ | Claude.Content_block.Tool_result r ->
6868+ let tool_use_id =
6969+ Claude.Content_block.Tool_result.tool_use_id r
7070+ in
7171+ let is_error =
7272+ Claude.Content_block.Tool_result.is_error r
7373+ |> Option.value ~default:false
7474+ in
7575+ if is_error then begin
7676+ Log.app (fun m ->
7777+ m "\n⚠️ Tool result error for %s:" tool_use_id);
7878+ match Claude.Content_block.Tool_result.content r with
7979+ | Some s -> Log.app (fun m -> m " %s" s)
8080+ | None -> ()
8181+ end
8282+ | _ -> ())
8383+ blocks
8484+ | _ -> ())
8585+ | Claude.Message.Result msg ->
8686+ if Claude.Message.Result.is_error msg then
8787+ Log.err (fun m -> m "\n❌ Error occurred!")
8888+ else Log.app (fun m -> m "\n✅ Success!");
8989+ (match Claude.Message.Result.total_cost_usd msg with
9090+ | Some cost -> Log.app (fun m -> m "💰 Cost: $%.6f" cost)
9191+ | None -> ());
9292+ Log.app (fun m ->
9393+ m "⏱️ Duration: %dms" (Claude.Message.Result.duration_ms msg))
9494+ | _ -> ())
9595+ messages;
85968697 Log.app (fun m -> m "\n====================================================");
8798 Log.app (fun m -> m "📊 Test Results:");
···89100 Log.app (fun m -> m " Write tool used: %b" !write_used);
9010191102 if !write_used then
9292- Log.app (fun m -> m " ✅ Permission callback successfully intercepted Write tool!")
9393- else
9494- Log.app (fun m -> m " ⚠️ Write tool was not used (unexpected)");
103103+ Log.app (fun m ->
104104+ m " ✅ Permission callback successfully intercepted Write tool!")
105105+ else Log.app (fun m -> m " ⚠️ Write tool was not used (unexpected)");
9510696107 Log.app (fun m -> m "====================================================");
97108 Log.app (fun m -> m "✨ Test complete!")
981099999-let main ~env =
100100- Switch.run @@ fun sw ->
101101- run_test ~sw ~env
110110+let main ~env = Switch.run @@ fun sw -> run_test ~sw ~env
102111103112(* Command-line interface *)
104113open Cmdliner
···126135 let info = Cmd.info "simple_permission_test" ~version:"1.0" ~doc in
127136 Cmd.v info (main_term env)
128137129129-let () =
130130- Eio_main.run @@ fun env ->
131131- exit (Cmd.eval (cmd env))
138138+let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))
+96-74
test/simulated_permissions.ml
···11-let src = Logs.Src.create "simulated_permissions" ~doc:"Simulated permission demonstration"
11+let src =
22+ Logs.Src.create "simulated_permissions"
33+ ~doc:"Simulated permission demonstration"
44+25module Log = (val Logs.src_log src : Logs.LOG)
3647(* Track granted permissions *)
58module PermissionState = struct
66- module StringSet = Set.Make(String)
99+ module StringSet = Set.Make (String)
1010+711 let granted = ref StringSet.empty
812 let denied = ref StringSet.empty
99-1313+1014 let grant tool =
1115 granted := StringSet.add tool !granted;
1216 denied := StringSet.remove tool !denied
1313-1717+1418 let deny tool =
1519 denied := StringSet.add tool !denied;
1620 granted := StringSet.remove tool !granted
1717-2121+1822 let is_granted tool = StringSet.mem tool !granted
1923 let is_denied tool = StringSet.mem tool !denied
2020-2424+2125 let _reset () =
2226 granted := StringSet.empty;
2327 denied := StringSet.empty
2424-2828+2529 let show () =
2630 Log.app (fun m -> m "\n📊 Permission Status:");
2731 if StringSet.is_empty !granted && StringSet.is_empty !denied then
2832 Log.app (fun m -> m " No permissions configured")
2933 else begin
3034 if not (StringSet.is_empty !granted) then
3131- Log.app (fun m -> m " ✅ Granted: %s"
3232- (StringSet.elements !granted |> String.concat ", "));
3333- if not (StringSet.is_empty !denied) then
3434- Log.app (fun m -> m " ❌ Denied: %s"
3535- (StringSet.elements !denied |> String.concat ", "))
3535+ Log.app (fun m ->
3636+ m " ✅ Granted: %s"
3737+ (StringSet.elements !granted |> String.concat ", "));
3838+ if not (StringSet.is_empty !denied) then
3939+ Log.app (fun m ->
4040+ m " ❌ Denied: %s" (StringSet.elements !denied |> String.concat ", "))
3641 end
3742end
38433944(* Example permission callback *)
4045let example_permission_callback ~tool_name ~input:_ ~context:_ =
4146 Log.app (fun m -> m "\n🔐 Permission Request for: %s" tool_name);
4242-4747+4348 (* Check current state *)
4449 if PermissionState.is_granted tool_name then begin
4550 Log.app (fun m -> m " → Auto-approved (previously granted)");
4651 Claude.Permissions.Result.allow ()
4747- end else if PermissionState.is_denied tool_name then begin
5252+ end
5353+ else if PermissionState.is_denied tool_name then begin
4854 Log.app (fun m -> m " → Auto-denied (previously denied)");
4955 Claude.Permissions.Result.deny
5056 ~message:(Printf.sprintf "Tool %s is blocked by policy" tool_name)
5157 ~interrupt:false ()
5252- end else begin
5858+ end
5959+ else begin
5360 (* Ask user *)
5461 Printf.printf " Allow %s? [y/n/always/never]: %!" tool_name;
5562 match read_line () |> String.lowercase_ascii with
···7380 ~interrupt:false ()
7481 | _ ->
7582 Log.app (fun m -> m " → Denied (invalid response)");
7676- Claude.Permissions.Result.deny
7777- ~message:"Invalid permission response"
8383+ Claude.Permissions.Result.deny ~message:"Invalid permission response"
7884 ~interrupt:false ()
7985 end
8086···8288let demo_permissions () =
8389 Log.app (fun m -> m "🎭 Permission System Demonstration");
8490 Log.app (fun m -> m "==================================\n");
8585-9191+8692 (* Simulate permission requests *)
8787- let tools = ["Read"; "Write"; "Bash"; "Edit"] in
9393+ let tools = [ "Read"; "Write"; "Bash"; "Edit" ] in
8894 let context = Claude.Permissions.Context.create () in
8989-9595+9096 Log.app (fun m -> m "This demo simulates permission requests.");
9197 Log.app (fun m -> m "You can respond with: y/n/always/never\n");
9292-9898+9399 (* Test each tool *)
9494- List.iter (fun tool ->
9595- let input =
9696- let open Jsont in
9797- Object ([
9898- (("file_path", Meta.none), String ("/example/path.txt", Meta.none))
9999- ], Meta.none)
100100- in
101101- let result = example_permission_callback
102102- ~tool_name:tool ~input ~context in
103103-104104- (* Show result *)
105105- (match result with
106106- | Claude.Permissions.Result.Allow _ ->
107107- Log.info (fun m -> m "Result: Permission granted for %s" tool)
108108- | Claude.Permissions.Result.Deny { message; _ } ->
109109- Log.info (fun m -> m "Result: Permission denied for %s - %s" tool message))
110110- ) tools;
111111-100100+ List.iter
101101+ (fun tool ->
102102+ let input =
103103+ let open Jsont in
104104+ Object
105105+ ( [
106106+ (("file_path", Meta.none), String ("/example/path.txt", Meta.none));
107107+ ],
108108+ Meta.none )
109109+ in
110110+ let result =
111111+ example_permission_callback ~tool_name:tool ~input ~context
112112+ in
113113+114114+ (* Show result *)
115115+ match result with
116116+ | Claude.Permissions.Result.Allow _ ->
117117+ Log.info (fun m -> m "Result: Permission granted for %s" tool)
118118+ | Claude.Permissions.Result.Deny { message; _ } ->
119119+ Log.info (fun m ->
120120+ m "Result: Permission denied for %s - %s" tool message))
121121+ tools;
122122+112123 (* Show final state *)
113124 PermissionState.show ()
114125···116127let demo_discovery () =
117128 Log.app (fun m -> m "\n\n🔍 Discovery Callback Demonstration");
118129 Log.app (fun m -> m "====================================\n");
119119-130130+120131 let discovered = ref [] in
121132 let callback = Claude.Permissions.discovery_callback discovered in
122122-133133+123134 (* Simulate some tool requests *)
124135 let requests =
125136 let open Jsont in
126137 [
127127- ("Read", Object ([
128128- (("file_path", Meta.none), String ("test.ml", Meta.none))
129129- ], Meta.none));
130130- ("Bash", Object ([
131131- (("command", Meta.none), String ("ls -la", Meta.none))
132132- ], Meta.none));
133133- ("Write", Object ([
134134- (("file_path", Meta.none), String ("output.txt", Meta.none))
135135- ], Meta.none));
138138+ ( "Read",
139139+ Object
140140+ ( [ (("file_path", Meta.none), String ("test.ml", Meta.none)) ],
141141+ Meta.none ) );
142142+ ( "Bash",
143143+ Object
144144+ ([ (("command", Meta.none), String ("ls -la", Meta.none)) ], Meta.none)
145145+ );
146146+ ( "Write",
147147+ Object
148148+ ( [ (("file_path", Meta.none), String ("output.txt", Meta.none)) ],
149149+ Meta.none ) );
136150 ]
137151 in
138138-152152+139153 Log.app (fun m -> m "Simulating tool requests with discovery callback...\n");
140140-141141- List.iter (fun (tool, input) ->
142142- Log.app (fun m -> m " Request: %s" tool);
143143- let context = Claude.Permissions.Context.create () in
144144- let _ = callback ~tool_name:tool ~input ~context in
145145- ()
146146- ) requests;
147147-154154+155155+ List.iter
156156+ (fun (tool, input) ->
157157+ Log.app (fun m -> m " Request: %s" tool);
158158+ let context = Claude.Permissions.Context.create () in
159159+ let _ = callback ~tool_name:tool ~input ~context in
160160+ ())
161161+ requests;
162162+148163 Log.app (fun m -> m "\n📋 Discovered permissions:");
149149- if !discovered = [] then
150150- Log.app (fun m -> m " None")
164164+ if !discovered = [] then Log.app (fun m -> m " None")
151165 else
152152- List.iter (fun rule ->
153153- Log.app (fun m -> m " - %s%s"
154154- (Claude.Permissions.Rule.tool_name rule)
155155- (match Claude.Permissions.Rule.rule_content rule with
156156- | Some content -> Printf.sprintf " (content: %s)" content
157157- | None -> ""))
158158- ) !discovered
166166+ List.iter
167167+ (fun rule ->
168168+ Log.app (fun m ->
169169+ m " - %s%s"
170170+ (Claude.Permissions.Rule.tool_name rule)
171171+ (match Claude.Permissions.Rule.rule_content rule with
172172+ | Some content -> Printf.sprintf " (content: %s)" content
173173+ | None -> "")))
174174+ !discovered
159175160176let main () =
161177 demo_permissions ();
···179195180196let cmd =
181197 let doc = "Demonstrate permission callbacks and discovery" in
182182- let man = [
183183- `S Manpage.s_description;
184184- `P "This program demonstrates how permission callbacks work in the Claude OCaml library.";
185185- `P "It simulates permission requests and shows how to implement custom callbacks.";
186186- ] in
198198+ let man =
199199+ [
200200+ `S Manpage.s_description;
201201+ `P
202202+ "This program demonstrates how permission callbacks work in the Claude \
203203+ OCaml library.";
204204+ `P
205205+ "It simulates permission requests and shows how to implement custom \
206206+ callbacks.";
207207+ ]
208208+ in
187209 let info = Cmd.info "simulated_permissions" ~version:"1.0" ~doc ~man in
188210 Cmd.v info main_term
189211190190-let () = exit (Cmd.eval cmd)212212+let () = exit (Cmd.eval cmd)
+155-103
test/structured_output_demo.ml
···1414 (* Define the JSON Schema for our expected output structure *)
1515 let analysis_schema =
1616 let open Jsont in
1717- Object ([
1818- (("type", Meta.none), String ("object", Meta.none));
1919- (("properties", Meta.none), Object ([
2020- (("file_count", Meta.none), Object ([
2121- (("type", Meta.none), String ("integer", Meta.none));
2222- (("description", Meta.none), String ("Total number of files analyzed", Meta.none))
2323- ], Meta.none));
2424- (("has_tests", Meta.none), Object ([
2525- (("type", Meta.none), String ("boolean", Meta.none));
2626- (("description", Meta.none), String ("Whether the codebase has test files", Meta.none))
2727- ], Meta.none));
2828- (("primary_language", Meta.none), Object ([
2929- (("type", Meta.none), String ("string", Meta.none));
3030- (("description", Meta.none), String ("The primary programming language used", Meta.none))
3131- ], Meta.none));
3232- (("complexity_rating", Meta.none), Object ([
3333- (("type", Meta.none), String ("string", Meta.none));
3434- (("enum", Meta.none), Array ([
3535- String ("low", Meta.none);
3636- String ("medium", Meta.none);
3737- String ("high", Meta.none)
3838- ], Meta.none));
3939- (("description", Meta.none), String ("Overall complexity rating", Meta.none))
4040- ], Meta.none));
4141- (("key_findings", Meta.none), Object ([
4242- (("type", Meta.none), String ("array", Meta.none));
4343- (("items", Meta.none), Object ([
4444- (("type", Meta.none), String ("string", Meta.none))
4545- ], Meta.none));
4646- (("description", Meta.none), String ("List of key findings from the analysis", Meta.none))
4747- ], Meta.none));
4848- ], Meta.none));
4949- (("required", Meta.none), Array ([
5050- String ("file_count", Meta.none);
5151- String ("has_tests", Meta.none);
5252- String ("primary_language", Meta.none);
5353- String ("complexity_rating", Meta.none);
5454- String ("key_findings", Meta.none)
5555- ], Meta.none));
5656- (("additionalProperties", Meta.none), Bool (false, Meta.none))
5757- ], Meta.none)
1717+ Object
1818+ ( [
1919+ (("type", Meta.none), String ("object", Meta.none));
2020+ ( ("properties", Meta.none),
2121+ Object
2222+ ( [
2323+ ( ("file_count", Meta.none),
2424+ Object
2525+ ( [
2626+ (("type", Meta.none), String ("integer", Meta.none));
2727+ ( ("description", Meta.none),
2828+ String ("Total number of files analyzed", Meta.none)
2929+ );
3030+ ],
3131+ Meta.none ) );
3232+ ( ("has_tests", Meta.none),
3333+ Object
3434+ ( [
3535+ (("type", Meta.none), String ("boolean", Meta.none));
3636+ ( ("description", Meta.none),
3737+ String
3838+ ("Whether the codebase has test files", Meta.none)
3939+ );
4040+ ],
4141+ Meta.none ) );
4242+ ( ("primary_language", Meta.none),
4343+ Object
4444+ ( [
4545+ (("type", Meta.none), String ("string", Meta.none));
4646+ ( ("description", Meta.none),
4747+ String
4848+ ( "The primary programming language used",
4949+ Meta.none ) );
5050+ ],
5151+ Meta.none ) );
5252+ ( ("complexity_rating", Meta.none),
5353+ Object
5454+ ( [
5555+ (("type", Meta.none), String ("string", Meta.none));
5656+ ( ("enum", Meta.none),
5757+ Array
5858+ ( [
5959+ String ("low", Meta.none);
6060+ String ("medium", Meta.none);
6161+ String ("high", Meta.none);
6262+ ],
6363+ Meta.none ) );
6464+ ( ("description", Meta.none),
6565+ String ("Overall complexity rating", Meta.none) );
6666+ ],
6767+ Meta.none ) );
6868+ ( ("key_findings", Meta.none),
6969+ Object
7070+ ( [
7171+ (("type", Meta.none), String ("array", Meta.none));
7272+ ( ("items", Meta.none),
7373+ Object
7474+ ( [
7575+ ( ("type", Meta.none),
7676+ String ("string", Meta.none) );
7777+ ],
7878+ Meta.none ) );
7979+ ( ("description", Meta.none),
8080+ String
8181+ ( "List of key findings from the analysis",
8282+ Meta.none ) );
8383+ ],
8484+ Meta.none ) );
8585+ ],
8686+ Meta.none ) );
8787+ ( ("required", Meta.none),
8888+ Array
8989+ ( [
9090+ String ("file_count", Meta.none);
9191+ String ("has_tests", Meta.none);
9292+ String ("primary_language", Meta.none);
9393+ String ("complexity_rating", Meta.none);
9494+ String ("key_findings", Meta.none);
9595+ ],
9696+ Meta.none ) );
9797+ (("additionalProperties", Meta.none), Bool (false, Meta.none));
9898+ ],
9999+ Meta.none )
58100 in
5910160102 (* Create structured output format from the schema *)
61103 let output_format = C.Structured_output.of_json_schema analysis_schema in
6210463105 (* Configure Claude with structured output *)
6464- let options = C.Options.default
106106+ let options =
107107+ C.Options.default
65108 |> C.Options.with_output_format output_format
6666- |> C.Options.with_allowed_tools ["Read"; "Glob"; "Grep"]
109109+ |> C.Options.with_allowed_tools [ "Read"; "Glob"; "Grep" ]
67110 |> C.Options.with_system_prompt
68111 "You are a code analysis assistant. Analyze codebases and provide \
69112 structured output matching the given JSON Schema."
···7511876119 (* Create Claude client and query *)
77120 Eio.Switch.run @@ fun sw ->
7878- let process_mgr = Eio.Stdenv.process_mgr env in
7979- let client = C.Client.create ~sw ~process_mgr ~options () in
121121+ let process_mgr = Eio.Stdenv.process_mgr env in
122122+ let client = C.Client.create ~sw ~process_mgr ~options () in
801238181- let prompt =
8282- "Please analyze the current codebase structure. Look at the files, \
8383- identify the primary language, count files, check for tests, assess \
8484- complexity, and provide key findings. Return your analysis in the \
8585- structured JSON format I specified."
8686- in
124124+ let prompt =
125125+ "Please analyze the current codebase structure. Look at the files, \
126126+ identify the primary language, count files, check for tests, assess \
127127+ complexity, and provide key findings. Return your analysis in the \
128128+ structured JSON format I specified."
129129+ in
871308888- Printf.printf "Sending query: %s\n\n" prompt;
8989- C.Client.query client prompt;
131131+ Printf.printf "Sending query: %s\n\n" prompt;
132132+ C.Client.query client prompt;
901339191- (* Process responses *)
9292- let messages = C.Client.receive client in
9393- Seq.iter (function
134134+ (* Process responses *)
135135+ let messages = C.Client.receive client in
136136+ Seq.iter
137137+ (function
94138 | C.Message.Assistant msg ->
95139 Printf.printf "\nAssistant response:\n";
9696- List.iter (function
9797- | C.Content_block.Text text ->
9898- Printf.printf " Text: %s\n" (C.Content_block.Text.text text)
9999- | C.Content_block.Tool_use tool ->
100100- Printf.printf " Using tool: %s\n" (C.Content_block.Tool_use.name tool)
101101- | _ -> ()
102102- ) (C.Message.Assistant.content msg)
103103-104104- | C.Message.Result result ->
140140+ List.iter
141141+ (function
142142+ | C.Content_block.Text text ->
143143+ Printf.printf " Text: %s\n" (C.Content_block.Text.text text)
144144+ | C.Content_block.Tool_use tool ->
145145+ Printf.printf " Using tool: %s\n"
146146+ (C.Content_block.Tool_use.name tool)
147147+ | _ -> ())
148148+ (C.Message.Assistant.content msg)
149149+ | C.Message.Result result -> (
105150 Printf.printf "\n=== Result ===\n";
106151 Printf.printf "Duration: %dms\n" (C.Message.Result.duration_ms result);
107152 Printf.printf "Cost: $%.4f\n"
108153 (Option.value (C.Message.Result.total_cost_usd result) ~default:0.0);
109154110155 (* Extract and display structured output *)
111111- (match C.Message.Result.structured_output result with
156156+ match C.Message.Result.structured_output result with
112157 | Some output ->
113158 Printf.printf "\n=== Structured Output ===\n";
114114- Printf.printf "%s\n\n" (Test_json_utils.to_string ~minify:false output);
159159+ Printf.printf "%s\n\n"
160160+ (Test_json_utils.to_string ~minify:false output);
115161116162 (* Parse the structured output *)
117117- let file_count = Test_json_utils.get_int output "file_count" |> Option.value ~default:0 in
118118- let has_tests = Test_json_utils.get_bool output "has_tests" |> Option.value ~default:false in
119119- let language = Test_json_utils.get_string output "primary_language" |> Option.value ~default:"unknown" in
120120- let complexity = Test_json_utils.get_string output "complexity_rating" |> Option.value ~default:"unknown" in
163163+ let file_count =
164164+ Test_json_utils.get_int output "file_count"
165165+ |> Option.value ~default:0
166166+ in
167167+ let has_tests =
168168+ Test_json_utils.get_bool output "has_tests"
169169+ |> Option.value ~default:false
170170+ in
171171+ let language =
172172+ Test_json_utils.get_string output "primary_language"
173173+ |> Option.value ~default:"unknown"
174174+ in
175175+ let complexity =
176176+ Test_json_utils.get_string output "complexity_rating"
177177+ |> Option.value ~default:"unknown"
178178+ in
121179 let findings =
122180 match Test_json_utils.get_array output "key_findings" with
123181 | Some items ->
124124- List.filter_map (fun json ->
125125- Test_json_utils.as_string json
126126- ) items
182182+ List.filter_map
183183+ (fun json -> Test_json_utils.as_string json)
184184+ items
127185 | None -> []
128186 in
129187···133191 Printf.printf "Primary Language: %s\n" language;
134192 Printf.printf "Complexity: %s\n" complexity;
135193 Printf.printf "Key Findings:\n";
136136- List.iter (fun finding ->
137137- Printf.printf " - %s\n" finding
138138- ) findings
139139-140140- | None ->
194194+ List.iter
195195+ (fun finding -> Printf.printf " - %s\n" finding)
196196+ findings
197197+ | None -> (
141198 Printf.printf "No structured output received\n";
142142- (match C.Message.Result.result result with
199199+ match C.Message.Result.result result with
143200 | Some text -> Printf.printf "Text result: %s\n" text
144201 | None -> ()))
145145-146146- | C.Message.System sys ->
147147- (match C.Message.System.subtype sys with
148148- | "init" ->
149149- Printf.printf "Session initialized\n"
202202+ | C.Message.System sys -> (
203203+ match C.Message.System.subtype sys with
204204+ | "init" -> Printf.printf "Session initialized\n"
150205 | _ -> ())
151151-152152- | _ -> ()
153153- ) messages;
206206+ | _ -> ())
207207+ messages;
154208155155- Printf.printf "\nDone!\n"
209209+ Printf.printf "\nDone!\n"
156210157211let () =
158212 Eio_main.run @@ fun env ->
159159- try
160160- run_codebase_analysis env
161161- with
162162- | C.Transport.CLI_not_found msg ->
163163- Printf.eprintf "Error: Claude CLI not found\n%s\n" msg;
164164- Printf.eprintf "Make sure 'claude' is installed and in your PATH\n";
165165- exit 1
166166- | C.Transport.Connection_error msg ->
167167- Printf.eprintf "Connection error: %s\n" msg;
168168- exit 1
169169- | exn ->
170170- Printf.eprintf "Unexpected error: %s\n" (Printexc.to_string exn);
171171- Printexc.print_backtrace stderr;
172172- exit 1
213213+ try run_codebase_analysis env with
214214+ | C.Transport.CLI_not_found msg ->
215215+ Printf.eprintf "Error: Claude CLI not found\n%s\n" msg;
216216+ Printf.eprintf "Make sure 'claude' is installed and in your PATH\n";
217217+ exit 1
218218+ | C.Transport.Connection_error msg ->
219219+ Printf.eprintf "Connection error: %s\n" msg;
220220+ exit 1
221221+ | exn ->
222222+ Printf.eprintf "Unexpected error: %s\n" (Printexc.to_string exn);
223223+ Printexc.print_backtrace stderr;
224224+ exit 1
+49-38
test/structured_output_simple.ml
···1212 (* Define a simple schema for a person's info *)
1313 let person_schema =
1414 let open Jsont in
1515- Object ([
1616- (("type", Meta.none), String ("object", Meta.none));
1717- (("properties", Meta.none), Object ([
1818- (("name", Meta.none), Object ([
1919- (("type", Meta.none), String ("string", Meta.none))
2020- ], Meta.none));
2121- (("age", Meta.none), Object ([
2222- (("type", Meta.none), String ("integer", Meta.none))
2323- ], Meta.none));
2424- (("occupation", Meta.none), Object ([
2525- (("type", Meta.none), String ("string", Meta.none))
2626- ], Meta.none));
2727- ], Meta.none));
2828- (("required", Meta.none), Array ([
2929- String ("name", Meta.none);
3030- String ("age", Meta.none);
3131- String ("occupation", Meta.none)
3232- ], Meta.none))
3333- ], Meta.none)
1515+ Object
1616+ ( [
1717+ (("type", Meta.none), String ("object", Meta.none));
1818+ ( ("properties", Meta.none),
1919+ Object
2020+ ( [
2121+ ( ("name", Meta.none),
2222+ Object
2323+ ( [ (("type", Meta.none), String ("string", Meta.none)) ],
2424+ Meta.none ) );
2525+ ( ("age", Meta.none),
2626+ Object
2727+ ( [ (("type", Meta.none), String ("integer", Meta.none)) ],
2828+ Meta.none ) );
2929+ ( ("occupation", Meta.none),
3030+ Object
3131+ ( [ (("type", Meta.none), String ("string", Meta.none)) ],
3232+ Meta.none ) );
3333+ ],
3434+ Meta.none ) );
3535+ ( ("required", Meta.none),
3636+ Array
3737+ ( [
3838+ String ("name", Meta.none);
3939+ String ("age", Meta.none);
4040+ String ("occupation", Meta.none);
4141+ ],
4242+ Meta.none ) );
4343+ ],
4444+ Meta.none )
3445 in
35463647 let output_format = C.Structured_output.of_json_schema person_schema in
37483838- let options = C.Options.default
4949+ let options =
5050+ C.Options.default
3951 |> C.Options.with_output_format output_format
4052 |> C.Options.with_max_turns 1
4153 in
···4355 Printf.printf "Asking Claude to provide structured data...\n\n";
44564557 Eio.Switch.run @@ fun sw ->
4646- let process_mgr = Eio.Stdenv.process_mgr env in
4747- let client = C.Client.create ~sw ~process_mgr ~options () in
5858+ let process_mgr = Eio.Stdenv.process_mgr env in
5959+ let client = C.Client.create ~sw ~process_mgr ~options () in
48604949- C.Client.query client
5050- "Tell me about a famous computer scientist. Provide their name, age, \
5151- and occupation in the exact JSON structure I specified.";
6161+ C.Client.query client
6262+ "Tell me about a famous computer scientist. Provide their name, age, and \
6363+ occupation in the exact JSON structure I specified.";
52645353- let messages = C.Client.receive_all client in
5454- List.iter (function
5555- | C.Message.Result result ->
6565+ let messages = C.Client.receive_all client in
6666+ List.iter
6767+ (function
6868+ | C.Message.Result result -> (
5669 Printf.printf "Response received!\n";
5757- (match C.Message.Result.structured_output result with
7070+ match C.Message.Result.structured_output result with
5871 | Some json ->
5972 Printf.printf "\nStructured Output:\n%s\n"
6073 (Test_json_utils.to_string ~minify:false json)
6161- | None ->
6262- Printf.printf "No structured output\n")
6363- | _ -> ()
6464- ) messages
7474+ | None -> Printf.printf "No structured output\n")
7575+ | _ -> ())
7676+ messages
65776678let () =
6779 Eio_main.run @@ fun env ->
6868- try
6969- simple_example env
7070- with exn ->
7171- Printf.eprintf "Error: %s\n" (Printexc.to_string exn);
7272- exit 1
8080+ try simple_example env
8181+ with exn ->
8282+ Printf.eprintf "Error: %s\n" (Printexc.to_string exn);
8383+ exit 1
+35-29
test/test_incoming.ml
···77 match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
88 | Ok (Incoming.Message (Message.User _)) ->
99 print_endline "✓ Decoded user message successfully"
1010- | Ok _ ->
1111- print_endline "✗ Wrong message type decoded"
1010+ | Ok _ -> print_endline "✗ Wrong message type decoded"
1211 | Error err ->
1313- Printf.printf "✗ Failed to decode user message: %s\n" (Jsont.Error.to_string err)
1212+ Printf.printf "✗ Failed to decode user message: %s\n"
1313+ (Jsont.Error.to_string err)
14141515let test_decode_assistant_message () =
1616- let json_str = {|{"type":"assistant","model":"claude-sonnet-4","content":[{"type":"text","text":"Hi"}]}|} in
1616+ let json_str =
1717+ {|{"type":"assistant","model":"claude-sonnet-4","content":[{"type":"text","text":"Hi"}]}|}
1818+ in
1719 match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
1820 | Ok (Incoming.Message (Message.Assistant _)) ->
1921 print_endline "✓ Decoded assistant message successfully"
2020- | Ok _ ->
2121- print_endline "✗ Wrong message type decoded"
2222+ | Ok _ -> print_endline "✗ Wrong message type decoded"
2223 | Error err ->
2323- Printf.printf "✗ Failed to decode assistant message: %s\n" (Jsont.Error.to_string err)
2424+ Printf.printf "✗ Failed to decode assistant message: %s\n"
2525+ (Jsont.Error.to_string err)
24262527let test_decode_system_message () =
2626- let json_str = {|{"type":"system","subtype":"init","data":{"session_id":"test-123"}}|} in
2828+ let json_str =
2929+ {|{"type":"system","subtype":"init","data":{"session_id":"test-123"}}|}
3030+ in
2731 match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
2832 | Ok (Incoming.Message (Message.System _)) ->
2933 print_endline "✓ Decoded system message successfully"
3030- | Ok _ ->
3131- print_endline "✗ Wrong message type decoded"
3434+ | Ok _ -> print_endline "✗ Wrong message type decoded"
3235 | Error err ->
3333- Printf.printf "✗ Failed to decode system message: %s\n" (Jsont.Error.to_string err)
3636+ Printf.printf "✗ Failed to decode system message: %s\n"
3737+ (Jsont.Error.to_string err)
34383539let test_decode_control_response () =
3636- let json_str = {|{"type":"control_response","response":{"subtype":"success","request_id":"test-req-1"}}|} in
4040+ let json_str =
4141+ {|{"type":"control_response","response":{"subtype":"success","request_id":"test-req-1"}}|}
4242+ in
3743 match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
3838- | Ok (Incoming.Control_response resp) ->
3939- (match resp.response with
4444+ | Ok (Incoming.Control_response resp) -> (
4545+ match resp.response with
4046 | Sdk_control.Response.Success s ->
4147 if s.request_id = "test-req-1" then
4248 print_endline "✓ Decoded control response successfully"
4343- else
4444- Printf.printf "✗ Wrong request_id: %s\n" s.request_id
4949+ else Printf.printf "✗ Wrong request_id: %s\n" s.request_id
4550 | Sdk_control.Response.Error _ ->
4651 print_endline "✗ Got error response instead of success")
4747- | Ok _ ->
4848- print_endline "✗ Wrong message type decoded"
5252+ | Ok _ -> print_endline "✗ Wrong message type decoded"
4953 | Error err ->
5050- Printf.printf "✗ Failed to decode control response: %s\n" (Jsont.Error.to_string err)
5454+ Printf.printf "✗ Failed to decode control response: %s\n"
5555+ (Jsont.Error.to_string err)
51565257let test_decode_control_response_error () =
5353- let json_str = {|{"type":"control_response","response":{"subtype":"error","request_id":"test-req-2","error":"Something went wrong"}}|} in
5858+ let json_str =
5959+ {|{"type":"control_response","response":{"subtype":"error","request_id":"test-req-2","error":"Something went wrong"}}|}
6060+ in
5461 match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
5555- | Ok (Incoming.Control_response resp) ->
5656- (match resp.response with
6262+ | Ok (Incoming.Control_response resp) -> (
6363+ match resp.response with
5764 | Sdk_control.Response.Error e ->
5858- if e.request_id = "test-req-2" && e.error = "Something went wrong" then
5959- print_endline "✓ Decoded control error response successfully"
6060- else
6161- Printf.printf "✗ Wrong error content\n"
6565+ if e.request_id = "test-req-2" && e.error = "Something went wrong"
6666+ then print_endline "✓ Decoded control error response successfully"
6767+ else Printf.printf "✗ Wrong error content\n"
6268 | Sdk_control.Response.Success _ ->
6369 print_endline "✗ Got success response instead of error")
6464- | Ok _ ->
6565- print_endline "✗ Wrong message type decoded"
7070+ | Ok _ -> print_endline "✗ Wrong message type decoded"
6671 | Error err ->
6767- Printf.printf "✗ Failed to decode control error response: %s\n" (Jsont.Error.to_string err)
7272+ Printf.printf "✗ Failed to decode control error response: %s\n"
7373+ (Jsont.Error.to_string err)
68746975let () =
7076 print_endline "Testing Incoming message codec...";
+5-8
test/test_json_utils.ml
···11(* Helper functions for JSON operations in tests using jsont codecs *)
2233-let to_string ?(minify=false) json =
33+let to_string ?(minify = false) json =
44 let format = if minify then Jsont.Minify else Jsont.Indent in
55 match Jsont_bytesrw.encode_string' ~format Jsont.json json with
66 | Ok s -> s
···8899(* Helper to decode an optional field with a given codec *)
1010let get_opt (type a) (codec : a Jsont.t) json key : a option =
1111- let field_codec = Jsont.Object.map ~kind:"field" (fun v -> v)
1111+ let field_codec =
1212+ Jsont.Object.map ~kind:"field" (fun v -> v)
1213 |> Jsont.Object.opt_mem key codec ~enc:Fun.id
1314 |> Jsont.Object.finish
1415 in
1515- match Jsont.Json.decode field_codec json with
1616- | Ok v -> v
1717- | Error _ -> None
1616+ match Jsont.Json.decode field_codec json with Ok v -> v | Error _ -> None
18171918let get_string json key = get_opt Jsont.string json key
2019let get_int json key = get_opt Jsont.int json key
2120let get_bool json key = get_opt Jsont.bool json key
2222-2323-let get_array json key =
2424- get_opt (Jsont.list Jsont.json) json key
2121+let get_array json key = get_opt (Jsont.list Jsont.json) json key
25222623let as_string json =
2724 match Jsont.Json.decode Jsont.string json with
+34-36
test/test_permissions.ml
···11open Eio.Std
2233let src = Logs.Src.create "test_permissions" ~doc:"Permission callback test"
44+45module Log = (val Logs.src_log src : Logs.LOG)
5667(* Simple auto-allow permission callback *)
···1314 Log.app (fun m -> m "================================");
14151516 (* Create options with custom permission callback *)
1616- let options = Claude.Options.create
1717- ~model:(Claude.Model.of_string "sonnet")
1818- ~permission_callback:auto_allow_callback
1919- () in
1717+ let options =
1818+ Claude.Options.create
1919+ ~model:(Claude.Model.of_string "sonnet")
2020+ ~permission_callback:auto_allow_callback ()
2121+ in
20222123 Log.app (fun m -> m "Creating client with permission callback...");
2222- let client = Claude.Client.create ~options ~sw
2323- ~process_mgr:env#process_mgr
2424- () in
2424+ let client =
2525+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ()
2626+ in
25272628 (* Simple query that will trigger tool use *)
2729 Log.app (fun m -> m "\n📤 Sending test query...");
2828- Claude.Client.query client
2929- "What is 2 + 2? Just give me the number.";
3030+ Claude.Client.query client "What is 2 + 2? Just give me the number.";
30313132 (* Process response *)
3233 let messages = Claude.Client.receive_all client in
3334 Log.app (fun m -> m "\n📨 Received %d messages" (List.length messages));
34353535- List.iter (fun msg ->
3636- match msg with
3737- | Claude.Message.Assistant msg ->
3838- List.iter (function
3939- | Claude.Content_block.Text t ->
4040- let text = Claude.Content_block.Text.text t in
4141- Log.app (fun m -> m "Claude: %s" text)
4242- | Claude.Content_block.Tool_use t ->
4343- Log.app (fun m -> m "🔧 Tool use: %s"
4444- (Claude.Content_block.Tool_use.name t))
4545- | _ -> ()
4646- ) (Claude.Message.Assistant.content msg)
4747- | Claude.Message.Result msg ->
4848- if Claude.Message.Result.is_error msg then
4949- Log.err (fun m -> m "❌ Error occurred!")
5050- else
5151- Log.app (fun m -> m "✅ Success!");
5252- Log.app (fun m -> m "Duration: %dms"
5353- (Claude.Message.Result.duration_ms msg))
5454- | _ -> ()
5555- ) messages;
3636+ List.iter
3737+ (fun msg ->
3838+ match msg with
3939+ | Claude.Message.Assistant msg ->
4040+ List.iter
4141+ (function
4242+ | Claude.Content_block.Text t ->
4343+ let text = Claude.Content_block.Text.text t in
4444+ Log.app (fun m -> m "Claude: %s" text)
4545+ | Claude.Content_block.Tool_use t ->
4646+ Log.app (fun m ->
4747+ m "🔧 Tool use: %s" (Claude.Content_block.Tool_use.name t))
4848+ | _ -> ())
4949+ (Claude.Message.Assistant.content msg)
5050+ | Claude.Message.Result msg ->
5151+ if Claude.Message.Result.is_error msg then
5252+ Log.err (fun m -> m "❌ Error occurred!")
5353+ else Log.app (fun m -> m "✅ Success!");
5454+ Log.app (fun m ->
5555+ m "Duration: %dms" (Claude.Message.Result.duration_ms msg))
5656+ | _ -> ())
5757+ messages;
56585759 Log.app (fun m -> m "\n================================");
5860 Log.app (fun m -> m "✨ Test complete!")
59616060-let main ~env =
6161- Switch.run @@ fun sw ->
6262- run_test ~sw ~env
6262+let main ~env = Switch.run @@ fun sw -> run_test ~sw ~env
63636464(* Command-line interface *)
6565open Cmdliner
···8686 let info = Cmd.info "test_permissions" ~version:"1.0" ~doc in
8787 Cmd.v info (main_term env)
88888989-let () =
9090- Eio_main.run @@ fun env ->
9191- exit (Cmd.eval (cmd env))
8989+let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))