···11-module Model = Model
11+module Err = Err
22+module Client = Client
33+module Options = Options
44+module Response = Response
55+module Handler = Handler
66+module Tool_input = Tool_input
27module Content_block = Content_block
38module Message = Message
44-module Control = Control
59module Permissions = Permissions
610module Hooks = Hooks
77-module Sdk_control = Sdk_control
88-module Incoming = Incoming
99-module Structured_output = Structured_output
1010-module Options = Options
1111+module Server_info = Server_info
1112module Transport = Transport
1212-module Client = Client
1313+module Model = Model
1414+module Proto = Proto
+138-104
lib/claude.mli
···15151616 {1 Architecture}
17171818- The library is structured into several focused modules:
1818+ The library is structured into two layers:
19192020- - {!Content_block}: Defines content blocks (text, tool use, tool results,
2121- thinking)
2222- - {!Message}: Messages exchanged with Claude (user, assistant, system,
2323- result)
2424- - {!Control}: Control flow messages for session management
2020+ {2 High-Level API}
2121+ - {!Client}: High-level client interface for interacting with Claude
2222+ - {!Response}: High-level response events from Claude
2323+ - {!Handler}: Object-oriented response handler with sensible defaults
2424+ - {!Options}: Configuration options for Claude sessions
2525 - {!Permissions}: Fine-grained permission system for tool usage
2626- - {!Options}: Configuration options for Claude sessions
2727- - {!Transport}: Low-level transport layer for CLI communication
2828- - {!Client}: High-level client interface for interacting with Claude
2626+ - {!Hooks}: Fully typed hook callbacks for event interception
2727+2828+ {2 Domain Types}
2929+ - {!Content_block}: Content blocks (text, tool use, tool results, thinking)
3030+ - {!Message}: Messages exchanged with Claude (user, assistant, system, result)
3131+ - {!Tool_input}: Opaque tool input with typed accessors
3232+ - {!Server_info}: Server capabilities and metadata
3333+3434+ {2 Wire Format (Advanced)}
3535+ - {!Proto}: Direct access to wire-format types and JSON codecs
29363030- {1 Basic Usage}
3737+ {1 Quick Start}
31383239 {[
3333- open Claude
4040+ open Eio.Std
4141+4242+ let () = Eio_main.run @@ fun env ->
4343+ Switch.run @@ fun sw ->
4444+ let client = Claude.Client.create ~sw
4545+ ~process_mgr:(Eio.Stdenv.process_mgr env) () in
34463535- (* Create a simple query *)
3636- let query_claude ~sw env prompt =
3737- let options = Options.default in
3838- Client.query ~sw env ~options prompt
4747+ Claude.Client.query client "What is 2+2?";
39484040- (* Process streaming responses *)
4141- let process_response 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
4949+ let handler = object
5050+ inherit Claude.Handler.default
5151+ method! on_text t = print_endline (Claude.Response.Text.content t)
5252+ end in
5353+5454+ Claude.Client.run client ~handler
5355 ]}
54565555- {1 Advanced Features}
5757+ {1 Response Handling}
5858+5959+ The library provides two ways to handle responses:
56605757- {2 Tool Permissions}
6161+ {2 Object-Oriented Handler (Recommended)}
58625959- Control which tools Claude can use and how:
6363+ Subclass {!Handler.default} and override only the methods you need:
60646165 {[
6262- let options =
6363- Options.default
6464- |> Options.with_allowed_tools [ "Read"; "Write"; "Bash" ]
6565- |> Options.with_permission_mode Permissions.Mode.Accept_edits
6666+ let my_handler = object
6767+ inherit Claude.Handler.default
6868+6969+ method! on_text t =
7070+ print_endline (Claude.Response.Text.content t)
7171+7272+ method! on_tool_use t =
7373+ Printf.printf "Tool: %s\n" (Claude.Response.Tool_use.name t)
7474+7575+ method! on_complete c =
7676+ Printf.printf "Done! Cost: $%.4f\n"
7777+ (Option.value ~default:0.0 (Claude.Response.Complete.total_cost_usd c))
7878+ end in
7979+8080+ Claude.Client.run client ~handler:my_handler
6681 ]}
67826868- {2 Custom Permission Callbacks}
8383+ {2 Functional Sequence}
69847070- Implement custom logic for tool approval:
8585+ For more control, use {!Client.receive} to get a lazy sequence:
71867287 {[
7373- let my_callback ~tool_name ~input ~context =
7474- if tool_name = "Bash" then
7575- Permissions.Result.deny ~message:"Bash not allowed" ~interrupt:false
7676- else Permissions.Result.allow ()
8888+ Claude.Client.receive client
8989+ |> Seq.iter (function
9090+ | Claude.Response.Text t -> print_endline (Claude.Response.Text.content t)
9191+ | Claude.Response.Complete c -> Printf.printf "Done!\n"
9292+ | _ -> ())
9393+ ]}
9494+9595+ {1 Tool Permissions}
9696+9797+ Control which tools Claude can use:
77989999+ {[
78100 let options =
7979- Options.default |> Options.with_permission_callback my_callback
101101+ Claude.Options.default
102102+ |> Claude.Options.with_allowed_tools [ "Read"; "Write"; "Bash" ]
103103+ |> Claude.Options.with_permission_mode Claude.Permissions.Mode.Accept_edits
80104 ]}
811058282- {2 System Prompts}
106106+ {2 Custom Permission Callbacks}
831078484- Customize Claude's behavior with system prompts:
108108+ Implement custom logic for tool approval:
8510986110 {[
111111+ let my_callback ctx =
112112+ if ctx.Claude.Permissions.tool_name = "Bash" then
113113+ Claude.Permissions.Decision.deny ~message:"Bash not allowed" ~interrupt:false
114114+ else
115115+ Claude.Permissions.Decision.allow ()
116116+87117 let options =
8888- Options.default
8989- |> Options.with_system_prompt
9090- "You are a helpful OCaml programming assistant."
9191- |> Options.with_append_system_prompt "Always use Jane Street style."
118118+ Claude.Options.default
119119+ |> Claude.Options.with_permission_callback my_callback
92120 ]}
931219494- {1 Logging}
122122+ {1 Typed Hooks}
951239696- 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:
124124+ Intercept and control tool execution with fully typed callbacks:
99125100126 {[
101101- (* Enable debug logging for message handling *)
102102- Logs.Src.set_level Message.src (Some Logs.Debug);
127127+ let hooks =
128128+ Claude.Hooks.empty
129129+ |> Claude.Hooks.on_pre_tool_use ~pattern:"Bash" (fun input ->
130130+ if String.is_prefix ~prefix:"rm" (input.tool_input |> Claude.Tool_input.get_string "command" |> Option.value ~default:"") then
131131+ Claude.Hooks.PreToolUse.deny ~reason:"Dangerous command" ()
132132+ else
133133+ Claude.Hooks.PreToolUse.continue ())
103134104104- (* Enable info logging for transport layer *)
105105- Logs.Src.set_level Transport.src (Some Logs.Info)
135135+ let options =
136136+ Claude.Options.default |> Claude.Options.with_hooks hooks
106137 ]}
107138108139 {1 Error Handling}
109140110110- The library uses exceptions for error handling. Common exceptions include:
111111- - [Invalid_argument]: For malformed JSON or invalid parameters
112112- - [Transport.Transport_error]: For CLI communication failures
113113- - [Message.Message_parse_error]: For message parsing failures
141141+ The library uses a structured exception type {!Err.E} for all errors:
142142+143143+ {[
144144+ try
145145+ Claude.Client.query client "Hello"
146146+ with Claude.Err.E err ->
147147+ Printf.eprintf "Error: %s\n" (Claude.Err.to_string err)
148148+ ]}
114149115115- {1 Example: Complete Session}
150150+ Error types include:
151151+ - {!Err.Cli_not_found}: Claude CLI not found
152152+ - {!Err.Process_error}: Process execution failure
153153+ - {!Err.Protocol_error}: JSON/protocol parsing error
154154+ - {!Err.Timeout}: Operation timed out
155155+ - {!Err.Permission_denied}: Tool permission denied
156156+ - {!Err.Hook_error}: Hook callback error
116157117117- {[
118118- let run_claude_session ~sw env =
119119- let options =
120120- Options.create ~allowed_tools:[ "Read"; "Write" ]
121121- ~permission_mode:Permissions.Mode.Accept_edits
122122- ~system_prompt:"You are an OCaml expert." ~max_thinking_tokens:10000
123123- ()
124124- in
158158+ {1 Logging}
125159126126- let prompt = "Write a function to calculate fibonacci numbers" in
127127- let messages = Client.query ~sw env ~options prompt in
160160+ The library uses the Logs library for structured logging. Each module has
161161+ its own log source allowing fine-grained control:
128162129129- 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
163163+ {[
164164+ Logs.Src.set_level Claude.Client.src (Some Logs.Debug);
165165+ Logs.Src.set_level Claude.Transport.src (Some Logs.Info)
149166 ]} *)
150167151151-(** {1 Modules} *)
168168+(** {1 Core Modules} *)
169169+170170+module Err = Err
171171+(** Error handling with structured exception type. *)
152172153173module Client = Client
154174(** High-level client interface for Claude interactions. *)
···156176module Options = Options
157177(** Configuration options for Claude sessions. *)
158178159159-module Model = Model
160160-(** Claude AI model identifiers with type-safe variants. *)
179179+module Response = Response
180180+(** High-level response events from Claude. *)
181181+182182+module Handler = Handler
183183+(** Object-oriented response handler with sensible defaults. *)
184184+185185+(** {1 Domain Types} *)
186186+187187+module Tool_input = Tool_input
188188+(** Opaque tool input with typed accessors. *)
161189162190module Content_block = Content_block
163191(** Content blocks for messages (text, tool use, tool results, thinking). *)
···165193module Message = Message
166194(** Messages exchanged with Claude (user, assistant, system, result). *)
167195168168-module Control = Control
169169-(** Control messages for session management. *)
170170-171196module Permissions = Permissions
172197(** Permission system for tool invocations. *)
173198174199module Hooks = Hooks
175175-(** Hooks system for event interception. *)
200200+(** Fully typed hook callbacks for event interception. *)
176201177177-module Sdk_control = Sdk_control
178178-(** SDK control protocol for dynamic configuration. *)
202202+module Server_info = Server_info
203203+(** Server capabilities and metadata. *)
179204180180-module Incoming = Incoming
181181-(** Discriminated union of all incoming message types from Claude CLI. *)
205205+module Model = Model
206206+(** Claude AI model identifiers. *)
182207183183-module Structured_output = Structured_output
184184-(** Structured output support using JSON Schema. *)
208208+(** {1 Infrastructure} *)
185209186210module Transport = Transport
187211(** Low-level transport layer for CLI communication. *)
212212+213213+(** {1 Wire Format (Advanced)}
214214+215215+ The {!Proto} module provides direct access to wire-format types and JSON
216216+ codecs. Use this for advanced scenarios like custom transports or debugging.
217217+218218+ Most users should use the high-level types above instead. *)
219219+220220+module Proto = Proto
221221+(** Wire-format types and JSON codecs. *)
+142-118
lib/client.ml
···2323 |> Result.map_error Jsont.Error.to_string
2424 |> Err.get_ok ~msg:""
25252626-(** Wire-level codec for permission responses to CLI. Uses camelCase field names
2727- as expected by the CLI protocol. *)
2828-module Permission_wire = struct
2929- type allow = { allow_behavior : string; allow_updated_input : Jsont.json }
3030- type deny = { deny_behavior : string; deny_message : string }
3131-3232- let allow_jsont : allow Jsont.t =
3333- let make allow_behavior allow_updated_input =
3434- { allow_behavior; allow_updated_input }
3535- in
3636- Jsont.Object.map ~kind:"AllowWire" make
3737- |> Jsont.Object.mem "behavior" Jsont.string ~enc:(fun r -> r.allow_behavior)
3838- |> Jsont.Object.mem "updatedInput" Jsont.json ~enc:(fun r ->
3939- r.allow_updated_input)
4040- |> Jsont.Object.finish
4141-4242- let deny_jsont : deny Jsont.t =
4343- let make deny_behavior deny_message = { deny_behavior; deny_message } in
4444- Jsont.Object.map ~kind:"DenyWire" make
4545- |> Jsont.Object.mem "behavior" Jsont.string ~enc:(fun r -> r.deny_behavior)
4646- |> Jsont.Object.mem "message" Jsont.string ~enc:(fun r -> r.deny_message)
4747- |> Jsont.Object.finish
4848-4949- let encode_allow ~updated_input =
5050- Jsont.Json.encode allow_jsont
5151- { allow_behavior = "allow"; allow_updated_input = updated_input }
5252- |> Err.get_ok ~msg:"Permission_wire.encode_allow: "
5353-5454- let encode_deny ~message =
5555- Jsont.Json.encode deny_jsont
5656- { deny_behavior = "deny"; deny_message = message }
5757- |> Err.get_ok ~msg:"Permission_wire.encode_deny: "
5858-end
5959-6026(** Wire-level codec for hook matcher configuration sent to CLI. *)
6127module Hook_matcher_wire = struct
6228 type t = { matcher : string option; hook_callback_ids : string list }
···80468147type t = {
8248 transport : Transport.t;
8383- permission_callback : Permissions.callback option;
8484- permission_log : Permissions.Rule.t list ref option;
8585- hook_callbacks : (string, Hooks.callback) Hashtbl.t;
4949+ mutable permission_callback : Permissions.callback option;
5050+ mutable permission_log : Permissions.Rule.t list ref option;
5151+ hook_callbacks : (string, Jsont.json -> Proto.Hooks.result) Hashtbl.t;
8652 mutable session_id : string option;
8753 control_responses : (string, Jsont.json) Hashtbl.t;
8854 control_mutex : Eio.Mutex.t;
···9864 match ctrl_req.request with
9965 | Sdk_control.Request.Permission req ->
10066 let tool_name = req.tool_name in
101101- let input = req.input in
6767+ let input_json = req.input in
10268 Log.info (fun m ->
10369 m "Permission request for tool '%s' with input: %s" tool_name
104104- (json_to_string input));
105105- (* Convert permission_suggestions to Context *)
7070+ (json_to_string input_json));
7171+ (* Convert permission_suggestions to suggested rules *)
10672 let suggestions = Option.value req.permission_suggestions ~default:[] in
107107- let context = Permissions.Context.create ~suggestions () in
7373+ let suggested_rules = Permissions.extract_rules_from_proto_updates suggestions in
7474+7575+ (* Convert input to Tool_input.t *)
7676+ let input = Tool_input.of_json input_json in
7777+7878+ (* Create context *)
7979+ let context : Permissions.context =
8080+ { tool_name; input; suggested_rules }
8181+ in
1088210983 Log.info (fun m ->
11084 m "Invoking permission callback for tool: %s" tool_name);
11185 let callback =
11286 Option.value t.permission_callback
113113- ~default:Permissions.default_allow_callback
8787+ ~default:Permissions.default_allow
11488 in
115115- let result = callback ~tool_name ~input ~context in
8989+ let decision = callback context in
11690 Log.info (fun m ->
11791 m "Permission callback returned: %s"
118118- (match result with
119119- | Permissions.Result.Allow _ -> "ALLOW"
120120- | Permissions.Result.Deny _ -> "DENY"));
9292+ (if Permissions.Decision.is_allow decision then "ALLOW" else "DENY"));
12193122122- (* Convert permission result to CLI format using wire codec *)
9494+ (* Convert permission decision to proto result *)
9595+ let proto_result = Permissions.Decision.to_proto_result decision in
9696+9797+ (* Encode to JSON *)
12398 let response_data =
124124- match result with
125125- | Permissions.Result.Allow
126126- { updated_input; updated_permissions = _; unknown = _ } ->
127127- let updated_input = Option.value updated_input ~default:input in
128128- Permission_wire.encode_allow ~updated_input
129129- | Permissions.Result.Deny { message; interrupt = _; unknown = _ } ->
130130- Permission_wire.encode_deny ~message
9999+ match Jsont.Json.encode Proto.Permissions.Result.jsont proto_result with
100100+ | Ok json -> json
101101+ | Error err ->
102102+ Log.err (fun m -> m "Failed to encode permission result: %s" err);
103103+ failwith "Permission result encoding failed"
131104 in
132105 let response =
133106 Control_response.success ~request_id ~response:(Some response_data)
···138111 | Sdk_control.Request.Hook_callback req -> (
139112 let callback_id = req.callback_id in
140113 let input = req.input in
141141- let tool_use_id = req.tool_use_id in
114114+ let _tool_use_id = req.tool_use_id in
142115 Log.info (fun m ->
143116 m "Hook callback request for callback_id: %s" callback_id);
144117145118 try
146119 let callback = Hashtbl.find t.hook_callbacks callback_id in
147147- let context = Hooks.Context.create () in
148148- let result = callback ~input ~tool_use_id ~context in
120120+ let result = callback input in
149121150122 let result_json =
151151- Jsont.Json.encode Hooks.result_jsont result
123123+ Jsont.Json.encode Proto.Hooks.result_jsont result
152124 |> Err.get_ok ~msg:"Failed to encode hook result: "
153125 in
154126 Log.debug (fun m ->
···197169 Hashtbl.replace t.control_responses request_id json;
198170 Eio.Condition.broadcast t.control_condition)
199171200200-let handle_messages t =
172172+let handle_raw_messages t =
201173 let rec loop () =
202174 match Transport.receive_line t.transport with
203175 | None ->
···207179 | Some line -> (
208180 (* Use unified Incoming codec for all message types *)
209181 match Jsont_bytesrw.decode_string' Incoming.jsont line with
210210- | Ok (Incoming.Message msg) ->
182182+ | Ok incoming ->
183183+ Seq.Cons (incoming, loop)
184184+ | Error err ->
185185+ Log.err (fun m ->
186186+ m "Failed to decode incoming message: %s\nLine: %s"
187187+ (Jsont.Error.to_string err)
188188+ line);
189189+ loop ())
190190+ in
191191+ Log.debug (fun m -> m "Starting message handler");
192192+ loop
193193+194194+let handle_messages t =
195195+ let raw_seq = handle_raw_messages t in
196196+ let rec loop raw_seq =
197197+ match raw_seq () with
198198+ | Seq.Nil -> Seq.Nil
199199+ | Seq.Cons (incoming, rest) -> (
200200+ match incoming with
201201+ | Incoming.Message msg ->
211202 Log.info (fun m -> m "ā %a" Message.pp msg);
212203213204 (* Extract session ID from system messages *)
···219210 Log.debug (fun m -> m "Stored session ID: %s" session_id))
220211 | _ -> ());
221212222222- Seq.Cons (msg, loop)
223223- | Ok (Incoming.Control_response resp) ->
213213+ (* Convert message to response events *)
214214+ let responses = Response.of_message msg in
215215+ emit_responses responses rest
216216+ | Incoming.Control_response resp ->
224217 handle_control_response t resp;
225225- loop ()
226226- | Ok (Incoming.Control_request ctrl_req) ->
218218+ loop rest
219219+ | Incoming.Control_request ctrl_req ->
227220 Log.info (fun m ->
228221 m "Received control request (request_id: %s)"
229222 ctrl_req.request_id);
230223 handle_control_request t ctrl_req;
231231- loop ()
232232- | Error err ->
233233- Log.err (fun m ->
234234- m "Failed to decode incoming message: %s\nLine: %s"
235235- (Jsont.Error.to_string err)
236236- line);
237237- loop ())
224224+ loop rest)
225225+226226+ and emit_responses responses rest =
227227+ match responses with
228228+ | [] -> loop rest
229229+ | r :: rs -> Seq.Cons (r, fun () -> emit_responses rs rest)
238230 in
239239- Log.debug (fun m -> m "Starting message handler");
240240- loop
231231+ loop raw_seq
241232242233let create ?(options = Options.default) ~sw ~process_mgr () =
243234 (* Automatically enable permission prompt tool when callback is configured
···273264 |> Option.iter (fun hooks_config ->
274265 Log.info (fun m -> m "Registering hooks...");
275266267267+ (* Get callbacks in wire format from the new Hooks API *)
268268+ let callbacks_by_event = Hooks.get_callbacks hooks_config in
269269+276270 (* Build hooks configuration with callback IDs as (string * Jsont.json) list *)
277271 let hooks_list =
278272 List.map
279273 (fun (event, matchers) ->
280280- let event_name = Hooks.event_to_string event in
274274+ let event_name = Proto.Hooks.event_to_string event in
281275 let matcher_wires =
282276 List.map
283283- (fun matcher ->
284284- let callback_ids =
285285- List.map
286286- (fun callback ->
287287- let callback_id =
288288- Printf.sprintf "hook_%d" !next_callback_id
289289- in
290290- incr next_callback_id;
291291- Hashtbl.add hook_callbacks callback_id callback;
292292- Log.debug (fun m ->
293293- m "Registered callback: %s for event: %s"
294294- callback_id event_name);
295295- callback_id)
296296- matcher.Hooks.callbacks
277277+ (fun (pattern, callback) ->
278278+ let callback_id =
279279+ Printf.sprintf "hook_%d" !next_callback_id
297280 in
281281+ incr next_callback_id;
282282+ Hashtbl.add hook_callbacks callback_id callback;
283283+ Log.debug (fun m ->
284284+ m "Registered callback: %s for event: %s"
285285+ callback_id event_name);
298286 Hook_matcher_wire.
299287 {
300300- matcher = matcher.Hooks.matcher;
301301- hook_callback_ids = callback_ids;
288288+ matcher = pattern;
289289+ hook_callback_ids = [callback_id];
302290 })
303291 matchers
304292 in
305293 (event_name, Hook_matcher_wire.encode matcher_wires))
306306- hooks_config
294294+ callbacks_by_event
307295 in
308296309297 (* Create initialize request using Sdk_control codec *)
···320308321309 t
322310323323-let query t prompt =
324324- let msg = Message.user_string prompt in
325325- Log.info (fun m -> m "ā %a" Message.pp msg);
326326- let json = Message.to_json msg in
327327- Transport.send t.transport json
328328-311311+(* Helper to send a message with proper "type" wrapper via Proto.Outgoing *)
329312let send_message t msg =
330313 Log.info (fun m -> m "ā %a" Message.pp msg);
331331- let json = Message.to_json msg in
314314+ let proto_msg = Message.to_proto msg in
315315+ let outgoing = Proto.Outgoing.Message proto_msg in
316316+ let json = Proto.Outgoing.to_json outgoing in
332317 Transport.send t.transport json
333318334334-let send_user_message t user_msg =
319319+let query t prompt =
320320+ let msg = Message.user_string prompt in
321321+ send_message t msg
322322+323323+let respond_to_tool t ~tool_use_id ~content ?(is_error = false) () =
324324+ let user_msg = Message.User.with_tool_result ~tool_use_id ~content ~is_error () in
335325 let msg = Message.User user_msg in
336336- Log.info (fun m -> m "ā %a" Message.pp msg);
337337- let json = Message.User.to_json user_msg in
338338- Transport.send t.transport json
326326+ send_message t msg
339327340340-let receive t = handle_messages t
328328+let respond_to_tools t responses =
329329+ let tool_results =
330330+ List.map
331331+ (fun (tool_use_id, content, is_error_opt) ->
332332+ let is_error = Option.value is_error_opt ~default:false in
333333+ Content_block.tool_result ~tool_use_id ~content ~is_error ())
334334+ responses
335335+ in
336336+ let user_msg = Message.User.of_blocks tool_results in
337337+ let msg = Message.User user_msg in
338338+ send_message t msg
339339+340340+let receive t = fun () -> handle_messages t
341341+342342+let run t ~handler =
343343+ Seq.iter (Handler.dispatch handler) (receive t)
341344342345let receive_all t =
343346 let rec collect acc seq =
344347 match seq () with
345348 | Seq.Nil ->
346349 Log.debug (fun m ->
347347- m "End of message sequence (%d messages)" (List.length acc));
350350+ m "End of response sequence (%d responses)" (List.length acc));
348351 List.rev acc
349349- | Seq.Cons ((Message.Result _ as msg), _) ->
350350- Log.debug (fun m -> m "Received final Result message");
351351- List.rev (msg :: acc)
352352- | Seq.Cons (msg, rest) -> collect (msg :: acc) rest
352352+ | Seq.Cons ((Response.Complete _ as resp), _) ->
353353+ Log.debug (fun m -> m "Received final Complete response");
354354+ List.rev (resp :: acc)
355355+ | Seq.Cons (resp, rest) -> collect (resp :: acc) rest
353356 in
354354- collect [] (handle_messages t)
357357+ collect [] (receive t)
355358356359let interrupt t = Transport.interrupt t.transport
357360358358-let discover_permissions t =
361361+let enable_permission_discovery t =
359362 let log = ref [] in
360360- let callback = Permissions.discovery_callback log in
361361- { t with permission_callback = Some callback; permission_log = Some log }
363363+ let callback = Permissions.discovery log in
364364+ t.permission_callback <- Some callback;
365365+ t.permission_log <- Some log
362366363363-let get_discovered_permissions t =
367367+let discovered_permissions t =
364368 t.permission_log |> Option.map ( ! ) |> Option.value ~default:[]
365365-366366-let with_permission_callback t callback =
367367- { t with permission_callback = Some callback }
368369369370(* Helper to send a control request and wait for response *)
370371let send_control_request t ~request_id request =
···427428428429let set_permission_mode t mode =
429430 let request_id = Printf.sprintf "set_perm_mode_%f" (Unix.gettimeofday ()) in
430430- let request = Sdk_control.Request.set_permission_mode ~mode () in
431431+ let proto_mode = Permissions.Mode.to_proto mode in
432432+ let request = Sdk_control.Request.set_permission_mode ~mode:proto_mode () in
431433 let _response = send_control_request t ~request_id request in
432434 Log.info (fun m ->
433435 m "Permission mode set to: %s" (Permissions.Mode.to_string mode))
···455457 m "Retrieved server info: %a"
456458 (Jsont.pp_value Sdk_control.Server_info.jsont ())
457459 server_info);
458458- server_info
460460+ Server_info.of_sdk_control server_info
461461+462462+module Advanced = struct
463463+ let send_message t msg = send_message t msg
464464+465465+ let send_user_message t user_msg =
466466+ let msg = Message.User user_msg in
467467+ send_message t msg
468468+469469+ let send_raw t control =
470470+ let json =
471471+ Jsont.Json.encode Sdk_control.jsont control
472472+ |> Err.get_ok ~msg:"Failed to encode control message: "
473473+ in
474474+ Log.info (fun m -> m "ā Raw control: %s" (json_to_string json));
475475+ Transport.send t.transport json
476476+477477+ let send_json t json =
478478+ Log.info (fun m -> m "ā Raw JSON: %s" (json_to_string json));
479479+ Transport.send t.transport json
480480+481481+ let receive_raw t = handle_raw_messages t
482482+end
+115-38
lib/client.mli
···6464 @param sw Eio switch for resource management
6565 @param process_mgr Eio process manager for spawning the Claude CLI *)
66666767+(** {1 Simple Query Interface} *)
6868+6769val query : t -> string -> unit
6870(** [query t prompt] sends a text message to Claude.
69717072 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. *)
7373+ messages with tool results or multiple content blocks, use
7474+ {!Advanced.send_message} instead. *)
73757474-val send_message : t -> Message.t -> unit
7575-(** [send_message t msg] sends a message to Claude.
7676+val respond_to_tool :
7777+ t -> tool_use_id:string -> content:string -> ?is_error:bool -> unit -> unit
7878+(** [respond_to_tool t ~tool_use_id ~content ?is_error ()] responds to a tool
7979+ use request.
76807777- Supports all message types including user messages with tool results. *)
8181+ @param tool_use_id The ID from the {!Response.Tool_use.t} event
8282+ @param content The result content (can be any string)
8383+ @param is_error Whether this is an error response (default: false) *)
8484+8585+val respond_to_tools : t -> (string * string * bool option) list -> unit
8686+(** [respond_to_tools t responses] responds to multiple tool use requests at
8787+ once.
78887979-val send_user_message : t -> Message.User.t -> unit
8080-(** [send_user_message t msg] sends a user message to Claude. *)
8989+ Each tuple is [(tool_use_id, content, is_error option)].
81908282-val receive : t -> Message.t Seq.t
8383-(** [receive t] returns a lazy sequence of messages from Claude.
9191+ Example:
9292+ {[
9393+ Client.respond_to_tools client
9494+ [
9595+ ("tool_use_123", "Success", None);
9696+ ("tool_use_456", "Error occurred", Some true);
9797+ ]
9898+ ]} *)
84998585- The sequence yields messages as they arrive from Claude, including:
8686- - {!constructor:Message.Assistant} - Claude's responses
8787- - {!constructor:Message.System} - System notifications
8888- - {!constructor:Message.Result} - Final result with usage statistics
100100+(** {1 Response Handling} *)
891019090- Control messages (permission requests, hook callbacks) are handled
9191- internally and not yielded to the sequence. *)
102102+val run : t -> handler:#Handler.handler -> unit
103103+(** [run t ~handler] processes all responses using the given handler.
921049393-val receive_all : t -> Message.t list
9494-(** [receive_all t] collects all messages into a list.
105105+ This is the recommended way to handle responses in an event-driven style.
106106+ The handler's methods will be called for each response event as it arrives.
951079696- 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. *)
108108+ Example:
109109+ {[
110110+ let my_handler = object
111111+ inherit Claude.Handler.default
112112+ method! on_text t = print_endline (Response.Text.content t)
113113+ method! on_complete c =
114114+ Printf.printf "Cost: $%.4f\n"
115115+ (Option.value ~default:0.0 (Response.Complete.total_cost_usd c))
116116+ end in
117117+ Client.query client "Hello";
118118+ Client.run client ~handler:my_handler
119119+ ]} *)
99120100100-val interrupt : t -> unit
101101-(** [interrupt t] sends an interrupt signal to stop Claude's execution. *)
121121+val receive : t -> Response.t Seq.t
122122+(** [receive t] returns a lazy sequence of responses from Claude.
102123103103-val discover_permissions : t -> t
104104-(** [discover_permissions t] enables permission discovery mode.
124124+ The sequence yields response events as they arrive from Claude, including:
125125+ - {!constructor:Response.Text} - Text content from assistant
126126+ - {!constructor:Response.Tool_use} - Tool invocation requests
127127+ - {!constructor:Response.Thinking} - Internal reasoning
128128+ - {!constructor:Response.Init} - Session initialization
129129+ - {!constructor:Response.Error} - Error events
130130+ - {!constructor:Response.Complete} - Final result with usage statistics
105131106106- In discovery mode, all tool usage is logged but allowed. Use
107107- {!get_discovered_permissions} to retrieve the list of permissions that were
108108- requested during execution.
132132+ Control messages (permission requests, hook callbacks) are handled
133133+ internally and not yielded to the sequence.
109134110110- This is useful for understanding what permissions your prompt requires. *)
135135+ For simple cases, prefer {!run} with a handler instead. *)
111136112112-val get_discovered_permissions : t -> Permissions.Rule.t list
113113-(** [get_discovered_permissions t] returns permissions discovered during
114114- execution.
137137+val receive_all : t -> Response.t list
138138+(** [receive_all t] collects all responses into a list.
115139116116- Only useful after enabling {!discover_permissions}. *)
140140+ This is a convenience function that consumes the {!receive} sequence. Use
141141+ this when you want to process all responses at once rather than streaming
142142+ them.
117143118118-val with_permission_callback : t -> Permissions.callback -> t
119119-(** [with_permission_callback t callback] updates the permission callback.
144144+ For most cases, prefer {!run} with a handler instead. *)
120145121121- Allows dynamically changing the permission callback without recreating the
122122- client. *)
146146+val interrupt : t -> unit
147147+(** [interrupt t] sends an interrupt signal to stop Claude's execution. *)
123148124124-(** {1 Dynamic Control Methods}
149149+(** {1 Dynamic Control}
125150126151 These methods allow you to change Claude's behavior mid-conversation without
127152 recreating the client. This is useful for:
···200225201226 @raise Failure if the model is invalid or unavailable *)
202227203203-val get_server_info : t -> Sdk_control.Server_info.t
228228+val get_server_info : t -> Server_info.t
204229(** [get_server_info t] retrieves server capabilities and metadata.
205230206231 Returns information about:
···212237 Useful for feature detection and debugging.
213238214239 @raise Failure if the server returns an error *)
240240+241241+(** {1 Permission Discovery} *)
242242+243243+val enable_permission_discovery : t -> unit
244244+(** [enable_permission_discovery t] enables permission discovery mode.
245245+246246+ In discovery mode, all tool usage is logged but allowed. Use
247247+ {!discovered_permissions} to retrieve the list of permissions that were
248248+ requested during execution.
249249+250250+ This is useful for understanding what permissions your prompt requires. *)
251251+252252+val discovered_permissions : t -> Permissions.Rule.t list
253253+(** [discovered_permissions t] returns permissions discovered during execution.
254254+255255+ Only useful after enabling {!enable_permission_discovery}. *)
256256+257257+(** {1 Advanced Interface}
258258+259259+ Low-level access to the protocol for advanced use cases. *)
260260+261261+module Advanced : sig
262262+ val send_message : t -> Message.t -> unit
263263+ (** [send_message t msg] sends a message to Claude.
264264+265265+ Supports all message types including user messages with tool results. *)
266266+267267+ val send_user_message : t -> Message.User.t -> unit
268268+ (** [send_user_message t msg] sends a user message to Claude. *)
269269+270270+ val send_raw : t -> Sdk_control.t -> unit
271271+ (** [send_raw t control] sends a raw SDK control message.
272272+273273+ This is for advanced use cases that need direct control protocol access. *)
274274+275275+ val send_json : t -> Jsont.json -> unit
276276+ (** [send_json t json] sends raw JSON to Claude.
277277+278278+ This is the lowest-level send operation. Use with caution. *)
279279+280280+ val receive_raw : t -> Incoming.t Seq.t
281281+ (** [receive_raw t] returns a lazy sequence of raw incoming messages.
282282+283283+ This includes all message types before Response conversion:
284284+ - {!Incoming.Message} - Regular messages
285285+ - {!Incoming.Control_response} - Control responses (normally handled
286286+ internally)
287287+ - {!Incoming.Control_request} - Control requests (normally handled
288288+ internally)
289289+290290+ Most users should use {!receive} or {!run} instead. *)
291291+end
+72-141
lib/content_block.ml
···33module Log = (val Logs.src_log src : Logs.LOG)
4455module Text = struct
66- type t = { text : string; unknown : Unknown.t }
77-88- let create text = { text; unknown = Unknown.empty }
99- let make text unknown = { text; unknown }
1010- let text t = t.text
1111- let unknown t = t.unknown
66+ type t = Proto.Content_block.Text.t
1271313- let jsont : t Jsont.t =
1414- Jsont.Object.map ~kind:"Text" make
1515- |> Jsont.Object.mem "text" Jsont.string ~enc:text
1616- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
1717- |> Jsont.Object.finish
88+ let text = Proto.Content_block.Text.text
99+ let of_proto proto = proto
1010+ let to_proto t = t
1811end
19122013module Tool_use = struct
2121- module Input = struct
2222- (* Dynamic JSON data for tool inputs with typed accessors using jsont decoders *)
2323- type t = Jsont.json
2424-2525- let jsont = Jsont.json
2626-2727- let of_string_pairs 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)
1414+ type t = Proto.Content_block.Tool_use.t
33153434- let of_assoc (assoc : (string * Jsont.json) list) : t =
3535- Jsont.Json.object'
3636- (List.map (fun (k, v) -> Jsont.Json.mem (Jsont.Json.name k) v) assoc)
1616+ let id = Proto.Content_block.Tool_use.id
1717+ let name = Proto.Content_block.Tool_use.name
37183838- (* Helper to decode an optional field with a given codec *)
3939- let get_opt (type a) (codec : a Jsont.t) t key : a option =
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 Ok v -> v | Error _ -> None
1919+ let input t =
2020+ Proto.Content_block.Tool_use.input t |> Tool_input.of_json
46214747- let get_string t key = get_opt Jsont.string t key
4848- let get_int t key = get_opt Jsont.int t key
4949- let get_bool t key = get_opt Jsont.bool t key
5050- let get_float t key = get_opt Jsont.number t key
2222+ let of_proto proto = proto
51235252- let keys t =
5353- (* Decode as object with all members captured as unknown *)
5454- match t with
5555- | Jsont.Object (members, _) ->
5656- List.map (fun ((name, _), _) -> name) members
5757- | _ -> []
5858- end
5959-6060- type t = { id : string; name : string; input : Input.t; unknown : Unknown.t }
6161-6262- let create ~id ~name ~input = { id; name; input; unknown = Unknown.empty }
6363- let make id name input unknown = { id; name; input; unknown }
6464- let id t = t.id
6565- let name t = t.name
6666- let input t = t.input
6767- let unknown t = t.unknown
6868-6969- let jsont : t Jsont.t =
7070- Jsont.Object.map ~kind:"Tool_use" make
7171- |> Jsont.Object.mem "id" Jsont.string ~enc:id
7272- |> Jsont.Object.mem "name" Jsont.string ~enc:name
7373- |> Jsont.Object.mem "input" Input.jsont ~enc:input
7474- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
7575- |> Jsont.Object.finish
2424+ let to_proto t = t
7625end
77267827module Tool_result = struct
7979- type t = {
8080- tool_use_id : string;
8181- content : string option;
8282- is_error : bool option;
8383- unknown : Unknown.t;
8484- }
2828+ type t = Proto.Content_block.Tool_result.t
85298686- let create ~tool_use_id ?content ?is_error () =
8787- { tool_use_id; content; is_error; unknown = Unknown.empty }
8888-8989- let make tool_use_id content is_error unknown =
9090- { tool_use_id; content; is_error; unknown }
9191-9292- let tool_use_id t = t.tool_use_id
9393- let content t = t.content
9494- let is_error t = t.is_error
9595- let unknown t = t.unknown
9696-9797- let jsont : t Jsont.t =
9898- Jsont.Object.map ~kind:"Tool_result" make
9999- |> Jsont.Object.mem "tool_use_id" Jsont.string ~enc:tool_use_id
100100- |> Jsont.Object.opt_mem "content" Jsont.string ~enc:content
101101- |> Jsont.Object.opt_mem "is_error" Jsont.bool ~enc:is_error
102102- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
103103- |> Jsont.Object.finish
3030+ let tool_use_id = Proto.Content_block.Tool_result.tool_use_id
3131+ let content = Proto.Content_block.Tool_result.content
3232+ let is_error = Proto.Content_block.Tool_result.is_error
3333+ let of_proto proto = proto
3434+ let to_proto t = t
10435end
1053610637module Thinking = struct
107107- type t = { thinking : string; signature : string; unknown : Unknown.t }
3838+ type t = Proto.Content_block.Thinking.t
10839109109- let create ~thinking ~signature =
110110- { thinking; signature; unknown = Unknown.empty }
111111-112112- let make thinking signature unknown = { thinking; signature; unknown }
113113- let thinking t = t.thinking
114114- let signature t = t.signature
115115- let unknown t = t.unknown
116116-117117- let jsont : t Jsont.t =
118118- Jsont.Object.map ~kind:"Thinking" make
119119- |> Jsont.Object.mem "thinking" Jsont.string ~enc:thinking
120120- |> Jsont.Object.mem "signature" Jsont.string ~enc:signature
121121- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
122122- |> Jsont.Object.finish
4040+ let thinking = Proto.Content_block.Thinking.thinking
4141+ let signature = Proto.Content_block.Thinking.signature
4242+ let of_proto proto = proto
4343+ let to_proto t = t
12344end
1244512546type t =
···12849 | Tool_result of Tool_result.t
12950 | Thinking of Thinking.t
13051131131-let text s = Text (Text.create s)
132132-let tool_use ~id ~name ~input = Tool_use (Tool_use.create ~id ~name ~input)
5252+let text s =
5353+ let proto = Proto.Content_block.text s in
5454+ match proto with
5555+ | Proto.Content_block.Text proto_text -> Text (Text.of_proto proto_text)
5656+ | _ -> failwith "Internal error: Proto.Content_block.text returned non-Text"
5757+5858+let tool_use ~id ~name ~input =
5959+ let json_input = Tool_input.to_json input in
6060+ let proto = Proto.Content_block.tool_use ~id ~name ~input:json_input in
6161+ match proto with
6262+ | Proto.Content_block.Tool_use proto_tool_use ->
6363+ Tool_use (Tool_use.of_proto proto_tool_use)
6464+ | _ ->
6565+ failwith "Internal error: Proto.Content_block.tool_use returned non-Tool_use"
1336613467let tool_result ~tool_use_id ?content ?is_error () =
135135- Tool_result (Tool_result.create ~tool_use_id ?content ?is_error ())
136136-137137-let thinking ~thinking ~signature =
138138- Thinking (Thinking.create ~thinking ~signature)
139139-140140-let jsont : t Jsont.t =
141141- let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
142142-143143- let case_text = case_map "text" Text.jsont (fun v -> Text v) in
144144- let case_tool_use =
145145- case_map "tool_use" Tool_use.jsont (fun v -> Tool_use v)
6868+ let proto =
6969+ Proto.Content_block.tool_result ~tool_use_id ?content ?is_error ()
14670 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
7171+ match proto with
7272+ | Proto.Content_block.Tool_result proto_tool_result ->
7373+ Tool_result (Tool_result.of_proto proto_tool_result)
7474+ | _ ->
7575+ failwith
7676+ "Internal error: Proto.Content_block.tool_result returned non-Tool_result"
15377154154- let enc_case = function
155155- | Text v -> Jsont.Object.Case.value case_text v
156156- | Tool_use v -> Jsont.Object.Case.value case_tool_use v
157157- | Tool_result v -> Jsont.Object.Case.value case_tool_result v
158158- | Thinking v -> Jsont.Object.Case.value case_thinking v
159159- in
7878+let thinking ~thinking ~signature =
7979+ let proto = Proto.Content_block.thinking ~thinking ~signature in
8080+ match proto with
8181+ | Proto.Content_block.Thinking proto_thinking ->
8282+ Thinking (Thinking.of_proto proto_thinking)
8383+ | _ ->
8484+ failwith
8585+ "Internal error: Proto.Content_block.thinking returned non-Thinking"
16086161161- 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
8787+let of_proto proto =
8888+ match proto with
8989+ | Proto.Content_block.Text t -> Text (Text.of_proto t)
9090+ | Proto.Content_block.Tool_use t -> Tool_use (Tool_use.of_proto t)
9191+ | Proto.Content_block.Tool_result t -> Tool_result (Tool_result.of_proto t)
9292+ | Proto.Content_block.Thinking t -> Thinking (Thinking.of_proto t)
17093171171- Jsont.Object.map ~kind:"Content_block" Fun.id
172172- |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
173173- ~tag_to_string:Fun.id ~tag_compare:String.compare
174174- |> Jsont.Object.finish
9494+let to_proto = function
9595+ | Text t -> Proto.Content_block.Text (Text.to_proto t)
9696+ | Tool_use t -> Proto.Content_block.Tool_use (Tool_use.to_proto t)
9797+ | Tool_result t -> Proto.Content_block.Tool_result (Tool_result.to_proto t)
9898+ | Thinking t -> Proto.Content_block.Thinking (Thinking.to_proto t)
17599176100let log_received t =
101101+ let proto = to_proto t in
177102 Log.debug (fun m ->
178178- m "Received content block: %a" (Jsont.pp_value jsont ()) t)
103103+ m "Received content block: %a"
104104+ (Jsont.pp_value Proto.Content_block.jsont ())
105105+ proto)
179106180107let log_sending t =
181181- Log.debug (fun m -> m "Sending content block: %a" (Jsont.pp_value jsont ()) t)
108108+ let proto = to_proto t in
109109+ Log.debug (fun m ->
110110+ m "Sending content block: %a"
111111+ (Jsont.pp_value Proto.Content_block.jsont ())
112112+ proto)
+43-93
lib/content_block.mli
···11-(** Content blocks for Claude messages.
11+(** Content blocks in messages. Opaque types without wire concerns.
2233- 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. *)
33+ This module provides opaque wrapper types around the proto content block
44+ types, hiding unknown fields and wire format details from the public API. *)
6576val src : Logs.Src.t
88-(** The log source for content block operations *)
77+(** Log source for content block operations. *)
98109(** {1 Text Blocks} *)
1110···1312 (** Plain text content blocks. *)
14131514 type t
1616- (** The type of text blocks. *)
1717-1818- val create : string -> t
1919- (** [create text] creates a new text block with the given text content. *)
1515+ (** The type of text blocks (opaque). *)
20162117 val text : t -> string
2218 (** [text t] returns the text content of the block. *)
23192424- val unknown : t -> Unknown.t
2525- (** [unknown t] returns any unknown fields from JSON parsing. *)
2020+ (** {1 Internal - for lib use only} *)
26212727- val jsont : t Jsont.t
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. *)
2222+ val of_proto : Proto.Content_block.Text.t -> t
2323+ (** [of_proto proto] wraps a proto text block. *)
2424+2525+ val to_proto : t -> Proto.Content_block.Text.t
2626+ (** [to_proto t] extracts the proto text block. *)
3127end
32283329(** {1 Tool Use Blocks} *)
···3531module Tool_use : sig
3632 (** Tool invocation requests from the assistant. *)
37333838- module Input : sig
3939- (** Tool input parameters. *)
4040-4141- type t
4242- (** Abstract type for tool inputs (opaque JSON). *)
4343-4444- val jsont : t Jsont.t
4545- (** [jsont] is the Jsont codec for tool inputs. *)
4646-4747- val of_string_pairs : (string * string) list -> t
4848- (** [of_string_pairs pairs] creates tool input from string key-value pairs.
4949- *)
5050-5151- val of_assoc : (string * Jsont.json) list -> t
5252- (** [of_assoc assoc] creates tool input from an association list. *)
5353-5454- val get_string : t -> string -> string option
5555- (** [get_string t key] returns the string value for [key], if present. *)
5656-5757- val get_int : t -> string -> int option
5858- (** [get_int t key] returns the integer value for [key], if present. *)
5959-6060- val get_bool : t -> string -> bool option
6161- (** [get_bool t key] returns the boolean value for [key], if present. *)
6262-6363- val get_float : t -> string -> float option
6464- (** [get_float t key] returns the float value for [key], if present. *)
6565-6666- val keys : t -> string list
6767- (** [keys t] returns all keys in the input. *)
6868- end
6969-7034 type t
7171- (** The type of tool use blocks. *)
7272-7373- val create : id:string -> name:string -> input:Input.t -> t
7474- (** [create ~id ~name ~input] creates a new tool use block.
7575- @param id Unique identifier for this tool invocation
7676- @param name Name of the tool to invoke
7777- @param input Parameters for the tool *)
3535+ (** The type of tool use blocks (opaque). *)
78367937 val id : t -> string
8038 (** [id t] returns the unique identifier of the tool use. *)
···8240 val name : t -> string
8341 (** [name t] returns the name of the tool being invoked. *)
84428585- val input : t -> Input.t
4343+ val input : t -> Tool_input.t
8644 (** [input t] returns the input parameters for the tool. *)
87458888- val unknown : t -> Unknown.t
8989- (** [unknown t] returns any unknown fields from JSON parsing. *)
4646+ (** {1 Internal - for lib use only} *)
4747+4848+ val of_proto : Proto.Content_block.Tool_use.t -> t
4949+ (** [of_proto proto] wraps a proto tool use block. *)
90509191- val jsont : t Jsont.t
9292- (** [jsont] is the Jsont codec for tool use blocks. Use
9393- [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
9494- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
5151+ val to_proto : t -> Proto.Content_block.Tool_use.t
5252+ (** [to_proto t] extracts the proto tool use block. *)
9553end
96549755(** {1 Tool Result Blocks} *)
···10058 (** Results from tool invocations. *)
1015910260 type t
103103- (** The type of tool result blocks. *)
104104-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.
109109- @param tool_use_id The ID of the corresponding tool use block
110110- @param content Optional result content
111111- @param is_error Whether the tool execution resulted in an error *)
6161+ (** The type of tool result blocks (opaque). *)
1126211363 val tool_use_id : t -> string
11464 (** [tool_use_id t] returns the ID of the corresponding tool use. *)
···11969 val is_error : t -> bool option
12070 (** [is_error t] returns whether this result represents an error. *)
12171122122- val unknown : t -> Unknown.t
123123- (** [unknown t] returns any unknown fields from JSON parsing. *)
7272+ (** {1 Internal - for lib use only} *)
7373+7474+ val of_proto : Proto.Content_block.Tool_result.t -> t
7575+ (** [of_proto proto] wraps a proto tool result block. *)
12476125125- val jsont : t Jsont.t
126126- (** [jsont] is the Jsont codec for tool result blocks. Use
127127- [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
128128- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
7777+ val to_proto : t -> Proto.Content_block.Tool_result.t
7878+ (** [to_proto t] extracts the proto tool result block. *)
12979end
1308013181(** {1 Thinking Blocks} *)
···13484 (** Assistant's internal reasoning blocks. *)
1358513686 type t
137137- (** The type of thinking blocks. *)
138138-139139- val create : thinking:string -> signature:string -> t
140140- (** [create ~thinking ~signature] creates a new thinking block.
141141- @param thinking The assistant's internal reasoning
142142- @param signature Cryptographic signature for verification *)
8787+ (** The type of thinking blocks (opaque). *)
1438814489 val thinking : t -> string
14590 (** [thinking t] returns the thinking content. *)
···14792 val signature : t -> string
14893 (** [signature t] returns the cryptographic signature. *)
14994150150- val unknown : t -> Unknown.t
151151- (** [unknown t] returns any unknown fields from JSON parsing. *)
9595+ (** {1 Internal - for lib use only} *)
9696+9797+ val of_proto : Proto.Content_block.Thinking.t -> t
9898+ (** [of_proto proto] wraps a proto thinking block. *)
15299153153- val jsont : t Jsont.t
154154- (** [jsont] is the Jsont codec for thinking blocks. Use
155155- [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
156156- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
100100+ val to_proto : t -> Proto.Content_block.Thinking.t
101101+ (** [to_proto t] extracts the proto thinking block. *)
157102end
158103159104(** {1 Content Block Union Type} *)
···166111 (** The type of content blocks, which can be text, tool use, tool result,
167112 or thinking. *)
168113114114+(** {1 Constructors} *)
115115+169116val text : string -> t
170117(** [text s] creates a text content block. *)
171118172172-val tool_use : id:string -> name:string -> input:Tool_use.Input.t -> t
119119+val tool_use : id:string -> name:string -> input:Tool_input.t -> t
173120(** [tool_use ~id ~name ~input] creates a tool use content block. *)
174121175122val tool_result :
···180127val thinking : thinking:string -> signature:string -> t
181128(** [thinking ~thinking ~signature] creates a thinking content block. *)
182129183183-val jsont : t Jsont.t
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. *)
130130+(** {1 Conversion} *)
131131+132132+val of_proto : Proto.Content_block.t -> t
133133+(** [of_proto proto] converts a proto content block to a lib content block. *)
134134+135135+val to_proto : t -> Proto.Content_block.t
136136+(** [to_proto t] converts a lib content block to a proto content block. *)
187137188138(** {1 Logging} *)
189139
···11-(** Error handling for the Claude protocol.
11+(** Error handling for claudeio. *)
2233- This module provides a protocol-specific exception and Result combinators
44- for handling JSON encoding/decoding errors in the Claude SDK. *)
33+type t =
44+ | Cli_not_found of string
55+ | Process_error of string
66+ | Connection_error of string
77+ | Protocol_error of string
88+ | Timeout of string
99+ | Permission_denied of { tool_name : string; message : string }
1010+ | Hook_error of { callback_id : string; message : string }
1111+ | Control_error of { request_id : string; message : string }
51266-exception Protocol_error of string
77-(** Raised when there is an error in the Claude protocol, such as JSON
88- encoding/decoding failures or malformed messages. *)
1313+exception E of t
1414+1515+let pp ppf = function
1616+ | Cli_not_found msg -> Fmt.pf ppf "CLI not found: %s" msg
1717+ | Process_error msg -> Fmt.pf ppf "Process error: %s" msg
1818+ | Connection_error msg -> Fmt.pf ppf "Connection error: %s" msg
1919+ | Protocol_error msg -> Fmt.pf ppf "Protocol error: %s" msg
2020+ | Timeout msg -> Fmt.pf ppf "Timeout: %s" msg
2121+ | Permission_denied { tool_name; message } ->
2222+ Fmt.pf ppf "Permission denied for tool '%s': %s" tool_name message
2323+ | Hook_error { callback_id; message } ->
2424+ Fmt.pf ppf "Hook error (callback_id=%s): %s" callback_id message
2525+ | Control_error { request_id; message } ->
2626+ Fmt.pf ppf "Control error (request_id=%s): %s" request_id message
9271010-(** [protocol_error msg] raises [Protocol_error msg]. *)
2828+let to_string err = Fmt.str "%a" pp err
2929+3030+let raise err = Stdlib.raise (E err)
3131+3232+(* Register exception printer for better error messages *)
3333+let () =
3434+ Printexc.register_printer (function
3535+ | E err -> Some (to_string err)
3636+ | _ -> None)
3737+3838+(** {1 Convenience Raisers} *)
3939+4040+let cli_not_found msg = raise (Cli_not_found msg)
4141+let process_error msg = raise (Process_error msg)
4242+let connection_error msg = raise (Connection_error msg)
1143let protocol_error msg = raise (Protocol_error msg)
4444+let timeout msg = raise (Timeout msg)
12451313-(** [get_ok ~msg r] returns [x] if [r] is [Ok x], or raises
1414- [Protocol_error (msg ^ e)] if [r] is [Error e]. *)
4646+let permission_denied ~tool_name ~message =
4747+ raise (Permission_denied { tool_name; message })
4848+4949+let hook_error ~callback_id ~message = raise (Hook_error { callback_id; message })
5050+let control_error ~request_id ~message = raise (Control_error { request_id; message })
5151+5252+(** {1 Result Helpers} *)
5353+1554let get_ok ~msg = function
1655 | Ok x -> x
1756 | Error e -> raise (Protocol_error (msg ^ e))
18571919-(** [get_ok' ~msg r] returns [x] if [r] is [Ok x], or raises
2020- [Invalid_argument (msg ^ e)] if [r] is [Error e]. Use this for user-facing
2121- parse errors where Invalid_argument is expected. *)
2258let get_ok' ~msg = function
2359 | Ok x -> x
2424- | Error e -> raise (Invalid_argument (msg ^ e))
6060+ | Error e -> raise (Protocol_error (msg ^ e))
+33-12
lib/err.mli
···11-(** Error handling for the Claude protocol.
11+(** Error handling for claudeio. *)
22+33+type t =
44+ | Cli_not_found of string
55+ | Process_error of string
66+ | Connection_error of string
77+ | Protocol_error of string
88+ | Timeout of string
99+ | Permission_denied of { tool_name : string; message : string }
1010+ | Hook_error of { callback_id : string; message : string }
1111+ | Control_error of { request_id : string; message : string }
1212+1313+exception E of t
1414+1515+val pp : Format.formatter -> t -> unit
1616+(** Pretty-print an error. *)
1717+1818+val to_string : t -> string
1919+(** Convert error to string. *)
22033- This module provides a protocol-specific exception and Result combinators
44- for handling JSON encoding/decoding errors in the Claude SDK. *)
2121+val raise : t -> 'a
2222+(** [raise err] raises [E err]. *)
52366-exception Protocol_error of string
77-(** Raised when there is an error in the Claude protocol, such as JSON
88- encoding/decoding failures or malformed messages. *)
2424+(** {1 Convenience Raisers} *)
9252626+val cli_not_found : string -> 'a
2727+val process_error : string -> 'a
2828+val connection_error : string -> 'a
1029val protocol_error : string -> 'a
1111-(** [protocol_error msg] raises [Protocol_error msg]. *)
3030+val timeout : string -> 'a
3131+val permission_denied : tool_name:string -> message:string -> 'a
3232+val hook_error : callback_id:string -> message:string -> 'a
3333+val control_error : request_id:string -> message:string -> 'a
3434+3535+(** {1 Result Helpers} *)
12361337val get_ok : msg:string -> ('a, string) result -> 'a
1414-(** [get_ok ~msg r] returns [x] if [r] is [Ok x], or raises
1515- [Protocol_error (msg ^ e)] if [r] is [Error e]. *)
3838+(** [get_ok ~msg result] returns the Ok value or raises Protocol_error with msg prefix. *)
16391740val get_ok' : msg:string -> ('a, string) result -> 'a
1818-(** [get_ok' ~msg r] returns [x] if [r] is [Ok x], or raises
1919- [Invalid_argument (msg ^ e)] if [r] is [Error e]. Use this for user-facing
2020- parse errors where Invalid_argument is expected. *)
4141+(** [get_ok' ~msg result] returns the Ok value or raises Protocol_error with string error. *)
+53
lib/handler.ml
···11+(** Object-oriented response handler implementations. *)
22+33+(** {1 Handler Interface} *)
44+55+class type handler =
66+ object
77+ method on_text : Response.Text.t -> unit
88+ method on_tool_use : Response.Tool_use.t -> unit
99+ method on_tool_result : Content_block.Tool_result.t -> unit
1010+ method on_thinking : Response.Thinking.t -> unit
1111+ method on_init : Response.Init.t -> unit
1212+ method on_error : Response.Error.t -> unit
1313+ method on_complete : Response.Complete.t -> unit
1414+ end
1515+1616+(** {1 Concrete Implementations} *)
1717+1818+class default : handler =
1919+ object
2020+ method on_text (_ : Response.Text.t) = ()
2121+ method on_tool_use (_ : Response.Tool_use.t) = ()
2222+ method on_tool_result (_ : Content_block.Tool_result.t) = ()
2323+ method on_thinking (_ : Response.Thinking.t) = ()
2424+ method on_init (_ : Response.Init.t) = ()
2525+ method on_error (_ : Response.Error.t) = ()
2626+ method on_complete (_ : Response.Complete.t) = ()
2727+ end
2828+2929+class virtual abstract =
3030+ object
3131+ method virtual on_text : Response.Text.t -> unit
3232+ method virtual on_tool_use : Response.Tool_use.t -> unit
3333+ method virtual on_tool_result : Content_block.Tool_result.t -> unit
3434+ method virtual on_thinking : Response.Thinking.t -> unit
3535+ method virtual on_init : Response.Init.t -> unit
3636+ method virtual on_error : Response.Error.t -> unit
3737+ method virtual on_complete : Response.Complete.t -> unit
3838+ end
3939+4040+(** {1 Dispatch Functions} *)
4141+4242+let dispatch (handler : #handler) (response : Response.t) =
4343+ match response with
4444+ | Response.Text t -> handler#on_text t
4545+ | Response.Tool_use t -> handler#on_tool_use t
4646+ | Response.Tool_result t -> handler#on_tool_result t
4747+ | Response.Thinking t -> handler#on_thinking t
4848+ | Response.Init t -> handler#on_init t
4949+ | Response.Error t -> handler#on_error t
5050+ | Response.Complete t -> handler#on_complete t
5151+5252+let dispatch_all (handler : #handler) (responses : Response.t list) =
5353+ List.iter (dispatch handler) responses
+159
lib/handler.mli
···11+(** Object-oriented response handler with sensible defaults.
22+33+ This module provides an object-oriented interface for handling response
44+ events from Claude. It offers both a concrete default implementation (where
55+ all methods do nothing) and an abstract base class (where all methods must
66+ be implemented).
77+88+ {1 Usage}
99+1010+ The simplest approach is to inherit from {!default} and override only the
1111+ methods you care about:
1212+1313+ {[
1414+ let my_handler = object
1515+ inherit Claude.Handler.default
1616+ method! on_text t = print_endline (Response.Text.content t)
1717+ method! on_complete c =
1818+ Printf.printf "Done! Cost: $%.4f\n"
1919+ (Option.value ~default:0.0 (Response.Complete.total_cost_usd c))
2020+ end
2121+ ]}
2222+2323+ For compile-time guarantees that all events are handled, inherit from
2424+ {!abstract}:
2525+2626+ {[
2727+ let complete_handler = object
2828+ inherit Claude.Handler.abstract
2929+ method on_text t = (* must implement *)
3030+ method on_tool_use t = (* must implement *)
3131+ method on_tool_result t = (* must implement *)
3232+ method on_thinking t = (* must implement *)
3333+ method on_init t = (* must implement *)
3434+ method on_error t = (* must implement *)
3535+ method on_complete t = (* must implement *)
3636+ end
3737+ ]} *)
3838+3939+(** {1 Handler Interface} *)
4040+4141+class type handler = object
4242+ method on_text : Response.Text.t -> unit
4343+ (** [on_text t] is called when text content is received from the assistant. *)
4444+4545+ method on_tool_use : Response.Tool_use.t -> unit
4646+ (** [on_tool_use t] is called when the assistant requests a tool invocation.
4747+ The caller is responsible for responding with
4848+ {!Client.respond_to_tool}. *)
4949+5050+ method on_tool_result : Content_block.Tool_result.t -> unit
5151+ (** [on_tool_result t] is called when a tool result is observed in the
5252+ message stream. This is typically an echo of what was sent to Claude. *)
5353+5454+ method on_thinking : Response.Thinking.t -> unit
5555+ (** [on_thinking t] is called when internal reasoning content is received. *)
5656+5757+ method on_init : Response.Init.t -> unit
5858+ (** [on_init t] is called when the session is initialized. This provides
5959+ session metadata like session_id and model. *)
6060+6161+ method on_error : Response.Error.t -> unit
6262+ (** [on_error t] is called when an error occurs. Errors can come from the
6363+ system (e.g., CLI errors) or from the assistant (e.g., rate limits). *)
6464+6565+ method on_complete : Response.Complete.t -> unit
6666+ (** [on_complete t] is called when the conversation completes. This provides
6767+ final metrics like duration, cost, and token usage. *)
6868+end
6969+(** The handler interface for processing response events.
7070+7171+ Each method corresponds to a variant of {!Response.t}. Handlers can be
7272+ passed to {!Client.run} to process responses in an event-driven style. *)
7373+7474+(** {1 Concrete Implementations} *)
7575+7676+class default : handler
7777+(** Default handler that does nothing for all events.
7878+7979+ This is the recommended base class for most use cases. Override only the
8080+ methods you need:
8181+8282+ {[
8383+ let handler = object
8484+ inherit Claude.Handler.default
8585+ method! on_text t = Printf.printf "Text: %s\n" (Response.Text.content t)
8686+ end
8787+ ]}
8888+8989+ Methods you don't override will simply be ignored, making this ideal for
9090+ prototyping and for cases where you only care about specific events. *)
9191+9292+class virtual abstract : object
9393+ method virtual on_text : Response.Text.t -> unit
9494+ (** [on_text t] must be implemented by subclasses. *)
9595+9696+ method virtual on_tool_use : Response.Tool_use.t -> unit
9797+ (** [on_tool_use t] must be implemented by subclasses. *)
9898+9999+ method virtual on_tool_result : Content_block.Tool_result.t -> unit
100100+ (** [on_tool_result t] must be implemented by subclasses. *)
101101+102102+ method virtual on_thinking : Response.Thinking.t -> unit
103103+ (** [on_thinking t] must be implemented by subclasses. *)
104104+105105+ method virtual on_init : Response.Init.t -> unit
106106+ (** [on_init t] must be implemented by subclasses. *)
107107+108108+ method virtual on_error : Response.Error.t -> unit
109109+ (** [on_error t] must be implemented by subclasses. *)
110110+111111+ method virtual on_complete : Response.Complete.t -> unit
112112+ (** [on_complete t] must be implemented by subclasses. *)
113113+end
114114+(** Abstract handler requiring all methods to be implemented.
115115+116116+ Use this when you want compile-time guarantees that all events are handled:
117117+118118+ {[
119119+ let handler = object
120120+ inherit Claude.Handler.abstract
121121+ method on_text t = (* required *)
122122+ method on_tool_use t = (* required *)
123123+ method on_tool_result t = (* required *)
124124+ method on_thinking t = (* required *)
125125+ method on_init t = (* required *)
126126+ method on_error t = (* required *)
127127+ method on_complete t = (* required *)
128128+ end
129129+ ]}
130130+131131+ The compiler will enforce that you implement all methods, ensuring no events
132132+ are silently ignored. *)
133133+134134+(** {1 Dispatch Functions} *)
135135+136136+val dispatch : #handler -> Response.t -> unit
137137+(** [dispatch handler response] dispatches a response event to the appropriate
138138+ handler method based on the response type.
139139+140140+ Example:
141141+ {[
142142+ let handler = object
143143+ inherit Claude.Handler.default
144144+ method! on_text t = print_endline (Response.Text.content t)
145145+ end in
146146+ dispatch handler (Response.Text text_event)
147147+ ]} *)
148148+149149+val dispatch_all : #handler -> Response.t list -> unit
150150+(** [dispatch_all handler responses] dispatches all response events to the
151151+ handler.
152152+153153+ This is equivalent to calling [List.iter (dispatch handler) responses] but
154154+ may be more convenient:
155155+156156+ {[
157157+ let responses = Client.receive_all client in
158158+ dispatch_all handler responses
159159+ ]} *)
+381-451
lib/hooks.ml
···2233module Log = (val Logs.src_log src : Logs.LOG)
4455-(** Hook events that can be intercepted *)
66-type event =
77- | Pre_tool_use
88- | Post_tool_use
99- | User_prompt_submit
1010- | Stop
1111- | Subagent_stop
1212- | Pre_compact
1313-1414-let event_to_string = function
1515- | Pre_tool_use -> "PreToolUse"
1616- | Post_tool_use -> "PostToolUse"
1717- | User_prompt_submit -> "UserPromptSubmit"
1818- | Stop -> "Stop"
1919- | Subagent_stop -> "SubagentStop"
2020- | Pre_compact -> "PreCompact"
2121-2222-let event_of_string = function
2323- | "PreToolUse" -> Pre_tool_use
2424- | "PostToolUse" -> Post_tool_use
2525- | "UserPromptSubmit" -> User_prompt_submit
2626- | "Stop" -> Stop
2727- | "SubagentStop" -> Subagent_stop
2828- | "PreCompact" -> Pre_compact
2929- | s -> raise (Invalid_argument (Printf.sprintf "Unknown hook event: %s" s))
3030-3131-let event_jsont : event Jsont.t =
3232- Jsont.enum
3333- [
3434- ("PreToolUse", Pre_tool_use);
3535- ("PostToolUse", Post_tool_use);
3636- ("UserPromptSubmit", User_prompt_submit);
3737- ("Stop", Stop);
3838- ("SubagentStop", Subagent_stop);
3939- ("PreCompact", Pre_compact);
4040- ]
4141-4242-(** Context provided to hook callbacks *)
4343-module Context = struct
4444- type t = {
4545- signal : unit option; (* Future: abort signal support *)
4646- unknown : Unknown.t;
4747- }
4848-4949- let create ?(signal = None) ?(unknown = Unknown.empty) () =
5050- { signal; unknown }
5151-5252- let signal t = t.signal
5353- let unknown t = t.unknown
5454-5555- let jsont : t Jsont.t =
5656- let make unknown = { signal = None; unknown } in
5757- Jsont.Object.map ~kind:"Context" make
5858- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
5959- |> Jsont.Object.finish
6060-end
6161-6262-(** Hook decision control *)
6363-type decision = Continue | Block
6464-6565-let decision_jsont : decision Jsont.t =
6666- Jsont.enum [ ("continue", Continue); ("block", Block) ]
6767-6868-(** Wire format for hook-specific output that includes hookEventName *)
6969-module Hook_specific_output = struct
7070- type t = { hook_event_name : event; output : Jsont.json }
7171-7272- let create ~event ~output = { hook_event_name = event; output }
7373-7474- let to_json t =
7575- (* Encode the event name *)
7676- let event_name_json =
7777- Jsont.Json.encode event_jsont t.hook_event_name
7878- |> Err.get_ok ~msg:"Hook_specific_output.to_json: event_name encoding"
7979- in
8080- (* Merge hookEventName into the output object *)
8181- match t.output with
8282- | Jsont.Object (members, meta) ->
8383- let hook_event_name_member =
8484- (Jsont.Json.name "hookEventName", event_name_json)
8585- in
8686- Jsont.Object (hook_event_name_member :: members, meta)
8787- | _ ->
8888- (* If output is not an object, wrap it *)
8989- Jsont.Object
9090- ( [
9191- ( Jsont.Json.name "hookEventName",
9292- event_name_json );
9393- ],
9494- Jsont.Meta.none )
9595-end
9696-9797-type result = {
9898- decision : decision option;
9999- system_message : string option;
100100- hook_specific_output : Jsont.json option;
101101- unknown : Unknown.t;
102102-}
103103-(** Generic hook result *)
104104-105105-let result_jsont : result Jsont.t =
106106- let make decision system_message hook_specific_output unknown =
107107- { decision; system_message; hook_specific_output; unknown }
108108- in
109109- Jsont.Object.map ~kind:"Result" make
110110- |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun r -> r.decision)
111111- |> Jsont.Object.opt_mem "systemMessage" Jsont.string ~enc:(fun r ->
112112- r.system_message)
113113- |> Jsont.Object.opt_mem "hookSpecificOutput" Jsont.json ~enc:(fun r ->
114114- r.hook_specific_output)
115115- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
116116- |> Jsont.Object.finish
55+(** {1 PreToolUse Hook} *)
1176118118-(** {1 PreToolUse Hook} *)
1197module PreToolUse = struct
1208 type input = {
1219 session_id : string;
12210 transcript_path : string;
12311 tool_name : string;
124124- tool_input : Jsont.json;
125125- unknown : Unknown.t;
1212+ tool_input : Tool_input.t;
12613 }
12714128128- type t = input
129129-130130- let session_id t = t.session_id
131131- let transcript_path t = t.transcript_path
132132- let tool_name t = t.tool_name
133133- let tool_input t = t.tool_input
134134- let unknown t = t.unknown
135135-136136- let input_jsont : input Jsont.t =
137137- let make session_id transcript_path tool_name tool_input unknown =
138138- { session_id; transcript_path; tool_name; tool_input; unknown }
139139- in
140140- Jsont.Object.map ~kind:"PreToolUseInput" make
141141- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
142142- |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
143143- |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
144144- |> Jsont.Object.mem "tool_input" Jsont.json ~enc:tool_input
145145- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
146146- |> Jsont.Object.finish
147147-148148- let of_json json =
149149- match Jsont.Json.decode input_jsont json with
150150- | Ok v -> v
151151- | Error msg -> raise (Invalid_argument ("PreToolUse: " ^ msg))
152152-153153- type permission_decision = [ `Allow | `Deny | `Ask ]
154154-155155- let permission_decision_jsont : permission_decision Jsont.t =
156156- Jsont.enum [ ("allow", `Allow); ("deny", `Deny); ("ask", `Ask) ]
1515+ type decision = Allow | Deny | Ask
1571615817 type output = {
159159- permission_decision : permission_decision option;
160160- permission_decision_reason : string option;
161161- updated_input : Jsont.json option;
162162- unknown : Unknown.t;
1818+ decision : decision option;
1919+ reason : string option;
2020+ updated_input : Tool_input.t option;
16321 }
16422165165- let output_jsont : output Jsont.t =
166166- let make permission_decision permission_decision_reason updated_input
167167- unknown =
168168- {
169169- permission_decision;
170170- permission_decision_reason;
171171- updated_input;
172172- unknown;
173173- }
174174- in
175175- Jsont.Object.map ~kind:"PreToolUseOutput" make
176176- |> Jsont.Object.opt_mem "permissionDecision" permission_decision_jsont
177177- ~enc:(fun o -> o.permission_decision)
178178- |> Jsont.Object.opt_mem "permissionDecisionReason" Jsont.string
179179- ~enc:(fun o -> o.permission_decision_reason)
180180- |> Jsont.Object.opt_mem "updatedInput" Jsont.json ~enc:(fun o ->
181181- o.updated_input)
182182- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun o -> o.unknown)
183183- |> Jsont.Object.finish
2323+ let allow ?reason ?updated_input () =
2424+ { decision = Some Allow; reason; updated_input }
18425185185- let output_to_json output =
186186- let inner =
187187- Jsont.Json.encode output_jsont output
188188- |> Err.get_ok ~msg:"PreToolUse.output_to_json: "
189189- in
190190- Hook_specific_output.(create ~event:Pre_tool_use ~output:inner |> to_json)
2626+ let deny ?reason () = { decision = Some Deny; reason; updated_input = None }
2727+ let ask ?reason () = { decision = Some Ask; reason; updated_input = None }
19128192192- let allow ?reason ?updated_input ?(unknown = Unknown.empty) () =
193193- {
194194- permission_decision = Some `Allow;
195195- permission_decision_reason = reason;
196196- updated_input;
197197- unknown;
198198- }
2929+ let continue () =
3030+ { decision = None; reason = None; updated_input = None }
19931200200- let deny ?reason ?(unknown = Unknown.empty) () =
201201- {
202202- permission_decision = Some `Deny;
203203- permission_decision_reason = reason;
204204- updated_input = None;
205205- unknown;
206206- }
3232+ type callback = input -> output
20733208208- let ask ?reason ?(unknown = Unknown.empty) () =
3434+ let input_of_proto proto =
20935 {
210210- permission_decision = Some `Ask;
211211- permission_decision_reason = reason;
212212- updated_input = None;
213213- unknown;
3636+ session_id = Proto.Hooks.PreToolUse.Input.session_id proto;
3737+ transcript_path = Proto.Hooks.PreToolUse.Input.transcript_path proto;
3838+ tool_name = Proto.Hooks.PreToolUse.Input.tool_name proto;
3939+ tool_input =
4040+ Tool_input.of_json (Proto.Hooks.PreToolUse.Input.tool_input proto);
21441 }
21542216216- let continue ?(unknown = Unknown.empty) () =
217217- {
218218- permission_decision = None;
219219- permission_decision_reason = None;
220220- updated_input = None;
221221- unknown;
222222- }
4343+ let output_to_proto output =
4444+ match output.decision with
4545+ | None -> Proto.Hooks.PreToolUse.Output.continue ()
4646+ | Some Allow ->
4747+ let updated_input =
4848+ Option.map Tool_input.to_json output.updated_input
4949+ in
5050+ Proto.Hooks.PreToolUse.Output.allow ?reason:output.reason
5151+ ?updated_input ()
5252+ | Some Deny -> Proto.Hooks.PreToolUse.Output.deny ?reason:output.reason ()
5353+ | Some Ask -> Proto.Hooks.PreToolUse.Output.ask ?reason:output.reason ()
22354end
2245522556(** {1 PostToolUse Hook} *)
5757+22658module PostToolUse = struct
22759 type input = {
22860 session_id : string;
22961 transcript_path : string;
23062 tool_name : string;
231231- tool_input : Jsont.json;
6363+ tool_input : Tool_input.t;
23264 tool_response : Jsont.json;
233233- unknown : Unknown.t;
23465 }
23566236236- type t = input
237237-238238- let session_id t = t.session_id
239239- let transcript_path t = t.transcript_path
240240- let tool_name t = t.tool_name
241241- let tool_input t = t.tool_input
242242- let tool_response t = t.tool_response
243243- let unknown t = t.unknown
244244-245245- let input_jsont : input Jsont.t =
246246- let make session_id transcript_path tool_name tool_input tool_response
247247- unknown =
248248- {
249249- session_id;
250250- transcript_path;
251251- tool_name;
252252- tool_input;
253253- tool_response;
254254- unknown;
255255- }
256256- in
257257- Jsont.Object.map ~kind:"PostToolUseInput" make
258258- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
259259- |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
260260- |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
261261- |> Jsont.Object.mem "tool_input" Jsont.json ~enc:tool_input
262262- |> Jsont.Object.mem "tool_response" Jsont.json ~enc:tool_response
263263- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
264264- |> Jsont.Object.finish
265265-266266- let of_json json =
267267- match Jsont.Json.decode input_jsont json with
268268- | Ok v -> v
269269- | Error msg -> raise (Invalid_argument ("PostToolUse: " ^ msg))
270270-27167 type output = {
272272- decision : decision option;
6868+ block : bool;
27369 reason : string option;
27470 additional_context : string option;
275275- unknown : Unknown.t;
27671 }
27772278278- let output_jsont : output Jsont.t =
279279- let make decision reason additional_context unknown =
280280- { decision; reason; additional_context; unknown }
281281- in
282282- Jsont.Object.map ~kind:"PostToolUseOutput" make
283283- |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o -> o.decision)
284284- |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
285285- |> Jsont.Object.opt_mem "additionalContext" Jsont.string ~enc:(fun o ->
286286- o.additional_context)
287287- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun o -> o.unknown)
288288- |> Jsont.Object.finish
7373+ let continue ?additional_context () =
7474+ { block = false; reason = None; additional_context }
28975290290- let output_to_json output =
291291- let inner =
292292- Jsont.Json.encode output_jsont output
293293- |> Err.get_ok ~msg:"PostToolUse.output_to_json: "
294294- in
295295- Hook_specific_output.(create ~event:Post_tool_use ~output:inner |> to_json)
7676+ let block ?reason ?additional_context () =
7777+ { block = true; reason; additional_context }
29678297297- let continue ?additional_context ?(unknown = Unknown.empty) () =
298298- { decision = None; reason = None; additional_context; unknown }
7979+ type callback = input -> output
29980300300- let block ?reason ?additional_context ?(unknown = Unknown.empty) () =
301301- { decision = Some Block; reason; additional_context; unknown }
8181+ let input_of_proto proto =
8282+ {
8383+ session_id = Proto.Hooks.PostToolUse.Input.session_id proto;
8484+ transcript_path = Proto.Hooks.PostToolUse.Input.transcript_path proto;
8585+ tool_name = Proto.Hooks.PostToolUse.Input.tool_name proto;
8686+ tool_input =
8787+ Tool_input.of_json (Proto.Hooks.PostToolUse.Input.tool_input proto);
8888+ tool_response = Proto.Hooks.PostToolUse.Input.tool_response proto;
8989+ }
9090+9191+ let output_to_proto output =
9292+ if output.block then
9393+ Proto.Hooks.PostToolUse.Output.block ?reason:output.reason
9494+ ?additional_context:output.additional_context ()
9595+ else
9696+ Proto.Hooks.PostToolUse.Output.continue
9797+ ?additional_context:output.additional_context ()
30298end
30399304100(** {1 UserPromptSubmit Hook} *)
101101+305102module UserPromptSubmit = struct
306103 type input = {
307104 session_id : string;
308105 transcript_path : string;
309106 prompt : string;
310310- unknown : Unknown.t;
311107 }
312108313313- type t = input
314314-315315- let session_id t = t.session_id
316316- let transcript_path t = t.transcript_path
317317- let prompt t = t.prompt
318318- let unknown t = t.unknown
319319-320320- let input_jsont : input Jsont.t =
321321- let make session_id transcript_path prompt unknown =
322322- { session_id; transcript_path; prompt; unknown }
323323- in
324324- Jsont.Object.map ~kind:"UserPromptSubmitInput" make
325325- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
326326- |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
327327- |> Jsont.Object.mem "prompt" Jsont.string ~enc:prompt
328328- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
329329- |> Jsont.Object.finish
330330-331331- let of_json json =
332332- match Jsont.Json.decode input_jsont json with
333333- | Ok v -> v
334334- | Error msg -> raise (Invalid_argument ("UserPromptSubmit: " ^ msg))
335335-336109 type output = {
337337- decision : decision option;
110110+ block : bool;
338111 reason : string option;
339112 additional_context : string option;
340340- unknown : Unknown.t;
341113 }
342114343343- let output_jsont : output Jsont.t =
344344- let make decision reason additional_context unknown =
345345- { decision; reason; additional_context; unknown }
346346- in
347347- Jsont.Object.map ~kind:"UserPromptSubmitOutput" make
348348- |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o -> o.decision)
349349- |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
350350- |> Jsont.Object.opt_mem "additionalContext" Jsont.string ~enc:(fun o ->
351351- o.additional_context)
352352- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun o -> o.unknown)
353353- |> Jsont.Object.finish
115115+ let continue ?additional_context () =
116116+ { block = false; reason = None; additional_context }
354117355355- let output_to_json output =
356356- let inner =
357357- Jsont.Json.encode output_jsont output
358358- |> Err.get_ok ~msg:"UserPromptSubmit.output_to_json: "
359359- in
360360- Hook_specific_output.(
361361- create ~event:User_prompt_submit ~output:inner |> to_json)
118118+ let block ?reason () = { block = true; reason; additional_context = None }
362119363363- let continue ?additional_context ?(unknown = Unknown.empty) () =
364364- { decision = None; reason = None; additional_context; unknown }
120120+ type callback = input -> output
121121+122122+ let input_of_proto proto =
123123+ {
124124+ session_id = Proto.Hooks.UserPromptSubmit.Input.session_id proto;
125125+ transcript_path =
126126+ Proto.Hooks.UserPromptSubmit.Input.transcript_path proto;
127127+ prompt = Proto.Hooks.UserPromptSubmit.Input.prompt proto;
128128+ }
365129366366- let block ?reason ?(unknown = Unknown.empty) () =
367367- { decision = Some Block; reason; additional_context = None; unknown }
130130+ let output_to_proto output =
131131+ if output.block then
132132+ Proto.Hooks.UserPromptSubmit.Output.block ?reason:output.reason ()
133133+ else
134134+ Proto.Hooks.UserPromptSubmit.Output.continue
135135+ ?additional_context:output.additional_context ()
368136end
369137370138(** {1 Stop Hook} *)
139139+371140module Stop = struct
372141 type input = {
373142 session_id : string;
374143 transcript_path : string;
375144 stop_hook_active : bool;
376376- unknown : Unknown.t;
377145 }
378146379379- type t = input
380380-381381- let session_id t = t.session_id
382382- let transcript_path t = t.transcript_path
383383- let stop_hook_active t = t.stop_hook_active
384384- let unknown t = t.unknown
385385-386386- let input_jsont : input Jsont.t =
387387- let make session_id transcript_path stop_hook_active unknown =
388388- { session_id; transcript_path; stop_hook_active; unknown }
389389- in
390390- Jsont.Object.map ~kind:"StopInput" make
391391- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
392392- |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
393393- |> Jsont.Object.mem "stop_hook_active" Jsont.bool ~enc:stop_hook_active
394394- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
395395- |> Jsont.Object.finish
396396-397397- let of_json json =
398398- match Jsont.Json.decode input_jsont json with
399399- | Ok v -> v
400400- | Error msg -> raise (Invalid_argument ("Stop: " ^ msg))
147147+ type output = { block : bool; reason : string option }
401148402402- type output = {
403403- decision : decision option;
404404- reason : string option;
405405- unknown : Unknown.t;
406406- }
407407-408408- let output_jsont : output Jsont.t =
409409- let make decision reason unknown = { decision; reason; unknown } in
410410- Jsont.Object.map ~kind:"StopOutput" make
411411- |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o -> o.decision)
412412- |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
413413- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun o -> o.unknown)
414414- |> Jsont.Object.finish
149149+ let continue () = { block = false; reason = None }
150150+ let block ?reason () = { block = true; reason }
415151416416- let output_to_json output =
417417- let inner =
418418- Jsont.Json.encode output_jsont output
419419- |> Err.get_ok ~msg:"Stop.output_to_json: "
420420- in
421421- Hook_specific_output.(create ~event:Stop ~output:inner |> to_json)
152152+ type callback = input -> output
422153423423- let continue ?(unknown = Unknown.empty) () =
424424- { decision = None; reason = None; unknown }
154154+ let input_of_proto proto =
155155+ {
156156+ session_id = Proto.Hooks.Stop.Input.session_id proto;
157157+ transcript_path = Proto.Hooks.Stop.Input.transcript_path proto;
158158+ stop_hook_active = Proto.Hooks.Stop.Input.stop_hook_active proto;
159159+ }
425160426426- let block ?reason ?(unknown = Unknown.empty) () =
427427- { decision = Some Block; reason; unknown }
161161+ let output_to_proto output =
162162+ if output.block then
163163+ Proto.Hooks.Stop.Output.block ?reason:output.reason ()
164164+ else Proto.Hooks.Stop.Output.continue ()
428165end
429166430430-(** {1 SubagentStop Hook} - Same structure as Stop *)
167167+(** {1 SubagentStop Hook} *)
168168+431169module SubagentStop = struct
432170 type input = Stop.input
433433- type t = input
434171 type output = Stop.output
435172436436- let session_id = Stop.session_id
437437- let transcript_path = Stop.transcript_path
438438- let stop_hook_active = Stop.stop_hook_active
439439- let unknown = Stop.unknown
440440- let input_jsont = Stop.input_jsont
441441- let of_json = Stop.of_json
442442- let output_jsont = Stop.output_jsont
443173 let continue = Stop.continue
444174 let block = Stop.block
445175446446- let output_to_json output =
447447- let inner =
448448- Jsont.Json.encode output_jsont output
449449- |> Err.get_ok ~msg:"SubagentStop.output_to_json: "
450450- in
451451- Hook_specific_output.(create ~event:Subagent_stop ~output:inner |> to_json)
176176+ type callback = input -> output
177177+178178+ let input_of_proto = Stop.input_of_proto
179179+180180+ (* Since Proto.Hooks.SubagentStop.Output.t = Proto.Hooks.Stop.Output.t,
181181+ we can use Stop.output_to_proto directly *)
182182+ let output_to_proto = Stop.output_to_proto
452183end
453184454185(** {1 PreCompact Hook} *)
186186+455187module PreCompact = struct
456456- type input = {
457457- session_id : string;
458458- transcript_path : string;
459459- unknown : Unknown.t;
460460- }
188188+ type input = { session_id : string; transcript_path : string }
461189462462- type t = input
190190+ type callback = input -> unit
463191464464- let session_id t = t.session_id
465465- let transcript_path t = t.transcript_path
466466- let unknown t = t.unknown
192192+ let input_of_proto proto =
193193+ {
194194+ session_id = Proto.Hooks.PreCompact.Input.session_id proto;
195195+ transcript_path = Proto.Hooks.PreCompact.Input.transcript_path proto;
196196+ }
197197+end
467198468468- let input_jsont : input Jsont.t =
469469- let make session_id transcript_path unknown =
470470- { session_id; transcript_path; unknown }
471471- in
472472- Jsont.Object.map ~kind:"PreCompactInput" make
473473- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
474474- |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
475475- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
476476- |> Jsont.Object.finish
199199+(** {1 Hook Configuration} *)
477200478478- let of_json json =
479479- match Jsont.Json.decode input_jsont json with
480480- | Ok v -> v
481481- | Error msg -> raise (Invalid_argument ("PreCompact: " ^ msg))
201201+(* Internal representation of hooks *)
202202+type hook_entry =
203203+ | PreToolUseHook of (string option * PreToolUse.callback)
204204+ | PostToolUseHook of (string option * PostToolUse.callback)
205205+ | UserPromptSubmitHook of UserPromptSubmit.callback
206206+ | StopHook of Stop.callback
207207+ | SubagentStopHook of SubagentStop.callback
208208+ | PreCompactHook of PreCompact.callback
482209483483- type output = unit (* No specific output for PreCompact *)
210210+type t = hook_entry list
484211485485- let output_to_json () =
486486- let inner = Jsont.Object ([], Jsont.Meta.none) in
487487- Hook_specific_output.(create ~event:Pre_compact ~output:inner |> to_json)
212212+let empty = []
488213489489- let continue () = ()
490490-end
214214+let on_pre_tool_use ?pattern callback config =
215215+ PreToolUseHook (pattern, callback) :: config
491216492492-type callback =
493493- input:Jsont.json -> tool_use_id:string option -> context:Context.t -> result
494494-(** {1 Generic Callback Type} *)
217217+let on_post_tool_use ?pattern callback config =
218218+ PostToolUseHook (pattern, callback) :: config
495219496496-type matcher = { matcher : string option; callbacks : callback list }
497497-(** {1 Matcher Configuration} *)
220220+let on_user_prompt_submit callback config =
221221+ UserPromptSubmitHook callback :: config
498222499499-type config = (event * matcher list) list
223223+let on_stop callback config = StopHook callback :: config
224224+let on_subagent_stop callback config = SubagentStopHook callback :: config
225225+let on_pre_compact callback config = PreCompactHook callback :: config
500226501501-(** {1 Result Builders} *)
502502-let continue ?system_message ?hook_specific_output ?(unknown = Unknown.empty) ()
503503- =
504504- { decision = None; system_message; hook_specific_output; unknown }
227227+(** {1 Internal - Conversion to Wire Format} *)
505228506506-let block ?system_message ?hook_specific_output ?(unknown = Unknown.empty) () =
507507- { decision = Some Block; system_message; hook_specific_output; unknown }
229229+let get_callbacks config =
230230+ (* Group hooks by event type *)
231231+ let pre_tool_use_hooks = ref [] in
232232+ let post_tool_use_hooks = ref [] in
233233+ let user_prompt_submit_hooks = ref [] in
234234+ let stop_hooks = ref [] in
235235+ let subagent_stop_hooks = ref [] in
236236+ let pre_compact_hooks = ref [] in
508237509509-(** {1 Matcher Builders} *)
510510-let matcher ?pattern callbacks = { matcher = pattern; callbacks }
238238+ List.iter
239239+ (function
240240+ | PreToolUseHook (pattern, callback) ->
241241+ pre_tool_use_hooks := (pattern, callback) :: !pre_tool_use_hooks
242242+ | PostToolUseHook (pattern, callback) ->
243243+ post_tool_use_hooks := (pattern, callback) :: !post_tool_use_hooks
244244+ | UserPromptSubmitHook callback ->
245245+ user_prompt_submit_hooks := (None, callback) :: !user_prompt_submit_hooks
246246+ | StopHook callback -> stop_hooks := (None, callback) :: !stop_hooks
247247+ | SubagentStopHook callback ->
248248+ subagent_stop_hooks := (None, callback) :: !subagent_stop_hooks
249249+ | PreCompactHook callback ->
250250+ pre_compact_hooks := (None, callback) :: !pre_compact_hooks)
251251+ config;
511252512512-(** {1 Config Builders} *)
513513-let empty = []
253253+ (* Convert each group to wire format *)
254254+ let result = [] in
514255515515-let add event matchers config = (event, matchers) :: config
256256+ (* PreToolUse *)
257257+ let result =
258258+ if !pre_tool_use_hooks <> [] then
259259+ let wire_callbacks =
260260+ List.map
261261+ (fun (pattern, callback) ->
262262+ let wire_callback json =
263263+ (* Decode JSON to Proto input *)
264264+ let proto_input =
265265+ match
266266+ Jsont.Json.decode Proto.Hooks.PreToolUse.Input.jsont json
267267+ with
268268+ | Ok input -> input
269269+ | Error msg ->
270270+ Log.err (fun m ->
271271+ m "PreToolUse: failed to decode input: %s" msg);
272272+ raise (Invalid_argument ("PreToolUse input: " ^ msg))
273273+ in
274274+ (* Convert to typed input *)
275275+ let typed_input = PreToolUse.input_of_proto proto_input in
276276+ (* Invoke user callback *)
277277+ let typed_output = callback typed_input in
278278+ (* Convert back to Proto output *)
279279+ let proto_output = PreToolUse.output_to_proto typed_output in
280280+ (* Encode as hook_specific_output *)
281281+ let hook_specific_output =
282282+ match
283283+ Jsont.Json.encode Proto.Hooks.PreToolUse.Output.jsont
284284+ proto_output
285285+ with
286286+ | Ok json -> json
287287+ | Error msg ->
288288+ failwith ("PreToolUse output encoding: " ^ msg)
289289+ in
290290+ (* Return wire format result *)
291291+ Proto.Hooks.continue ~hook_specific_output ()
292292+ in
293293+ (pattern, wire_callback))
294294+ !pre_tool_use_hooks
295295+ in
296296+ (Proto.Hooks.Pre_tool_use, wire_callbacks) :: result
297297+ else result
298298+ in
516299517517-(** {1 JSON Conversion} *)
518518-let result_to_json result =
519519- match Jsont.Json.encode result_jsont result with
520520- | Ok json -> json
521521- | Error msg -> failwith ("result_to_json: " ^ msg)
300300+ (* PostToolUse *)
301301+ let result =
302302+ if !post_tool_use_hooks <> [] then
303303+ let wire_callbacks =
304304+ List.map
305305+ (fun (pattern, callback) ->
306306+ let wire_callback json =
307307+ let proto_input =
308308+ match
309309+ Jsont.Json.decode Proto.Hooks.PostToolUse.Input.jsont json
310310+ with
311311+ | Ok input -> input
312312+ | Error msg ->
313313+ Log.err (fun m ->
314314+ m "PostToolUse: failed to decode input: %s" msg);
315315+ raise (Invalid_argument ("PostToolUse input: " ^ msg))
316316+ in
317317+ let typed_input = PostToolUse.input_of_proto proto_input in
318318+ let typed_output = callback typed_input in
319319+ let proto_output = PostToolUse.output_to_proto typed_output in
320320+ let hook_specific_output =
321321+ match
322322+ Jsont.Json.encode Proto.Hooks.PostToolUse.Output.jsont
323323+ proto_output
324324+ with
325325+ | Ok json -> json
326326+ | Error msg ->
327327+ failwith ("PostToolUse output encoding: " ^ msg)
328328+ in
329329+ if typed_output.block then
330330+ Proto.Hooks.block ~hook_specific_output ()
331331+ else Proto.Hooks.continue ~hook_specific_output ()
332332+ in
333333+ (pattern, wire_callback))
334334+ !post_tool_use_hooks
335335+ in
336336+ (Proto.Hooks.Post_tool_use, wire_callbacks) :: result
337337+ else result
338338+ in
522339523523-(** Wire codec for hook matcher in protocol format *)
524524-module Protocol_matcher_wire = struct
525525- type t = { matcher : string option; callbacks : Jsont.json list }
340340+ (* UserPromptSubmit *)
341341+ let result =
342342+ if !user_prompt_submit_hooks <> [] then
343343+ let wire_callbacks =
344344+ List.map
345345+ (fun (pattern, callback) ->
346346+ let wire_callback json =
347347+ let proto_input =
348348+ match
349349+ Jsont.Json.decode Proto.Hooks.UserPromptSubmit.Input.jsont
350350+ json
351351+ with
352352+ | Ok input -> input
353353+ | Error msg ->
354354+ Log.err (fun m ->
355355+ m "UserPromptSubmit: failed to decode input: %s" msg);
356356+ raise (Invalid_argument ("UserPromptSubmit input: " ^ msg))
357357+ in
358358+ let typed_input = UserPromptSubmit.input_of_proto proto_input in
359359+ let typed_output = callback typed_input in
360360+ let proto_output =
361361+ UserPromptSubmit.output_to_proto typed_output
362362+ in
363363+ let hook_specific_output =
364364+ match
365365+ Jsont.Json.encode Proto.Hooks.UserPromptSubmit.Output.jsont
366366+ proto_output
367367+ with
368368+ | Ok json -> json
369369+ | Error msg ->
370370+ failwith ("UserPromptSubmit output encoding: " ^ msg)
371371+ in
372372+ if typed_output.block then
373373+ Proto.Hooks.block ~hook_specific_output ()
374374+ else Proto.Hooks.continue ~hook_specific_output ()
375375+ in
376376+ (pattern, wire_callback))
377377+ !user_prompt_submit_hooks
378378+ in
379379+ (Proto.Hooks.User_prompt_submit, wire_callbacks) :: result
380380+ else result
381381+ in
526382527527- let jsont : t Jsont.t =
528528- let make matcher callbacks = { matcher; callbacks } in
529529- Jsont.Object.map ~kind:"ProtocolMatcher" make
530530- |> Jsont.Object.opt_mem "matcher" Jsont.string ~enc:(fun r -> r.matcher)
531531- |> Jsont.Object.mem "callbacks" (Jsont.list Jsont.json) ~enc:(fun r ->
532532- r.callbacks)
533533- |> Jsont.Object.finish
383383+ (* Stop *)
384384+ let result =
385385+ if !stop_hooks <> [] then
386386+ let wire_callbacks =
387387+ List.map
388388+ (fun (pattern, callback) ->
389389+ let wire_callback json =
390390+ let proto_input =
391391+ match Jsont.Json.decode Proto.Hooks.Stop.Input.jsont json with
392392+ | Ok input -> input
393393+ | Error msg ->
394394+ Log.err (fun m ->
395395+ m "Stop: failed to decode input: %s" msg);
396396+ raise (Invalid_argument ("Stop input: " ^ msg))
397397+ in
398398+ let typed_input = Stop.input_of_proto proto_input in
399399+ let typed_output = callback typed_input in
400400+ let proto_output = Stop.output_to_proto typed_output in
401401+ let hook_specific_output =
402402+ match
403403+ Jsont.Json.encode Proto.Hooks.Stop.Output.jsont proto_output
404404+ with
405405+ | Ok json -> json
406406+ | Error msg -> failwith ("Stop output encoding: " ^ msg)
407407+ in
408408+ if typed_output.block then
409409+ Proto.Hooks.block ~hook_specific_output ()
410410+ else Proto.Hooks.continue ~hook_specific_output ()
411411+ in
412412+ (pattern, wire_callback))
413413+ !stop_hooks
414414+ in
415415+ (Proto.Hooks.Stop, wire_callbacks) :: result
416416+ else result
417417+ in
534418535535- let encode m =
536536- match Jsont.Json.encode jsont m with
537537- | Ok json -> json
538538- | Error msg -> failwith ("Protocol_matcher_wire.encode: " ^ msg)
539539-end
419419+ (* SubagentStop *)
420420+ let result =
421421+ if !subagent_stop_hooks <> [] then
422422+ let wire_callbacks =
423423+ List.map
424424+ (fun (pattern, callback) ->
425425+ let wire_callback json =
426426+ let proto_input =
427427+ match
428428+ Jsont.Json.decode Proto.Hooks.SubagentStop.Input.jsont json
429429+ with
430430+ | Ok input -> input
431431+ | Error msg ->
432432+ Log.err (fun m ->
433433+ m "SubagentStop: failed to decode input: %s" msg);
434434+ raise (Invalid_argument ("SubagentStop input: " ^ msg))
435435+ in
436436+ let typed_input = SubagentStop.input_of_proto proto_input in
437437+ let typed_output = callback typed_input in
438438+ let proto_output = SubagentStop.output_to_proto typed_output in
439439+ let hook_specific_output =
440440+ match
441441+ Jsont.Json.encode Proto.Hooks.SubagentStop.Output.jsont
442442+ proto_output
443443+ with
444444+ | Ok json -> json
445445+ | Error msg ->
446446+ failwith ("SubagentStop output encoding: " ^ msg)
447447+ in
448448+ if typed_output.block then
449449+ Proto.Hooks.block ~hook_specific_output ()
450450+ else Proto.Hooks.continue ~hook_specific_output ()
451451+ in
452452+ (pattern, wire_callback))
453453+ !subagent_stop_hooks
454454+ in
455455+ (Proto.Hooks.Subagent_stop, wire_callbacks) :: result
456456+ else result
457457+ in
540458541541-let config_to_protocol_format config =
542542- let hooks_dict =
543543- List.map
544544- (fun (event, matchers) ->
545545- let event_name = event_to_string event in
546546- let matchers_json =
547547- List.map
548548- (fun m ->
549549- (* matcher and hookCallbackIds will be filled in by client *)
550550- Protocol_matcher_wire.encode
551551- { matcher = m.matcher; callbacks = [] })
552552- matchers
553553- in
554554- Jsont.Json.mem
555555- (Jsont.Json.name event_name)
556556- (Jsont.Json.list matchers_json))
557557- config
459459+ (* PreCompact *)
460460+ let result =
461461+ if !pre_compact_hooks <> [] then
462462+ let wire_callbacks =
463463+ List.map
464464+ (fun (pattern, callback) ->
465465+ let wire_callback json =
466466+ let proto_input =
467467+ match
468468+ Jsont.Json.decode Proto.Hooks.PreCompact.Input.jsont json
469469+ with
470470+ | Ok input -> input
471471+ | Error msg ->
472472+ Log.err (fun m ->
473473+ m "PreCompact: failed to decode input: %s" msg);
474474+ raise (Invalid_argument ("PreCompact input: " ^ msg))
475475+ in
476476+ let typed_input = PreCompact.input_of_proto proto_input in
477477+ (* Invoke user callback (returns unit) *)
478478+ callback typed_input;
479479+ (* PreCompact has no specific output *)
480480+ Proto.Hooks.continue ()
481481+ in
482482+ (pattern, wire_callback))
483483+ !pre_compact_hooks
484484+ in
485485+ (Proto.Hooks.Pre_compact, wire_callbacks) :: result
486486+ else result
558487 in
559559- Jsont.Json.object' hooks_dict
488488+489489+ List.rev result
+223-237
lib/hooks.mli
···11-(** Claude Code Hooks System
11+(** Fully typed hook callbacks.
2233 Hooks allow you to intercept and control events in Claude Code sessions,
44- such as tool usage, prompt submission, and session stops.
44+ using fully typed OCaml values instead of raw JSON.
5566 {1 Overview}
7788- Hooks are organized by event type, with each event having:
99- - A typed input structure (accessible via submodules)
1010- - A typed output structure for responses
88+ This module provides a high-level, type-safe interface to hooks. Each hook
99+ type has:
1010+ - Fully typed input records using {!Tool_input.t}
1111+ - Fully typed output records
1112 - Helper functions for common responses
1313+ - Conversion functions to/from wire format ({!Proto.Hooks})
12141315 {1 Example Usage}
1416···1618 open Eio.Std
17191820 (* Block dangerous bash commands *)
1919- let get_string json key =
2020- match json with
2121- | Jsont.Object (members, _) ->
2222- List.find_map (fun ((name, _), value) ->
2323- if name = key then
2424- match value with
2525- | Jsont.String (s, _) -> Some s
2626- | _ -> None
2727- else None
2828- ) members
2929- | _ -> None
3030- in
3131- let block_rm_rf ~input ~tool_use_id:_ ~context:_ =
3232- let hook = Hooks.PreToolUse.of_json input in
3333- if Hooks.PreToolUse.tool_name hook = "Bash" then
3434- let tool_input = Hooks.PreToolUse.tool_input hook in
3535- match get_string tool_input "command" with
2121+ let block_rm_rf input =
2222+ if input.Hooks.PreToolUse.tool_name = "Bash" then
2323+ match Tool_input.get_string input.tool_input "command" with
3624 | Some cmd when String.contains cmd "rm -rf" ->
3737- let output = Hooks.PreToolUse.deny ~reason:"Dangerous command" () in
3838- Hooks.continue
3939- ~hook_specific_output:(Hooks.PreToolUse.output_to_json output)
4040- ()
4141- | _ -> Hooks.continue ()
4242- else Hooks.continue ()
2525+ Hooks.PreToolUse.deny ~reason:"Dangerous command" ()
2626+ | _ -> Hooks.PreToolUse.continue ()
2727+ else Hooks.PreToolUse.continue ()
43284429 let hooks =
4530 Hooks.empty
4646- |> Hooks.add Hooks.Pre_tool_use [
4747- Hooks.matcher ~pattern:"Bash" [block_rm_rf]
4848- ]
3131+ |> Hooks.on_pre_tool_use ~pattern:"Bash" block_rm_rf
49325050- let options = Claude.Options.create ~hooks:(Some hooks) () in
3333+ let options = Claude.Options.create ~hooks () in
5134 let client = Claude.Client.create ~options ~sw ~process_mgr () in
5235 ]} *)
53365437val src : Logs.Src.t
5538(** The log source for hooks *)
56395757-(** {1 Hook Events} *)
5858-5959-(** Hook event types *)
6060-type event =
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 *)
6767-6868-val event_to_string : event -> string
6969-val event_of_string : string -> event
7070-val event_jsont : event Jsont.t
7171-7272-(** {1 Context} *)
7373-7474-module Context : sig
7575- type t = { signal : unit option; unknown : Unknown.t }
7676-7777- val create : ?signal:unit option -> ?unknown:Unknown.t -> unit -> t
7878- val signal : t -> unit option
7979- val unknown : t -> Unknown.t
8080- val jsont : t Jsont.t
8181-end
8282-8383-(** {1 Decisions} *)
8484-8585-type decision =
8686- | Continue (** Allow the action to proceed *)
8787- | Block (** Block the action *)
8888-8989-val decision_jsont : decision Jsont.t
9090-9191-(** {1 Generic Hook Result} *)
9292-9393-type result = {
9494- decision : decision option;
9595- system_message : string option;
9696- hook_specific_output : Jsont.json option;
9797- unknown : Unknown.t;
9898-}
9999-(** Generic result structure for hooks *)
100100-101101-val result_jsont : result Jsont.t
102102-103103-(** {1 Typed Hook Modules} *)
4040+(** {1 Hook Types} *)
1044110542(** PreToolUse hook - fires before tool execution *)
10643module PreToolUse : sig
4444+ (** {2 Input} *)
4545+10746 type input = {
10847 session_id : string;
10948 transcript_path : string;
11049 tool_name : string;
111111- tool_input : Jsont.json;
112112- unknown : Unknown.t;
5050+ tool_input : Tool_input.t;
11351 }
114114- (** Typed input for PreToolUse hooks *)
5252+ (** Input provided to PreToolUse hooks. *)
11553116116- type t = input
5454+ (** {2 Output} *)
11755118118- val of_json : Jsont.json -> t
119119- (** Parse hook input from JSON *)
5656+ type decision =
5757+ | Allow
5858+ | Deny
5959+ | Ask
6060+ (** Permission decision for tool usage. *)
12061121121- val session_id : t -> string
122122- (** {2 Accessors} *)
6262+ type output = {
6363+ decision : decision option;
6464+ reason : string option;
6565+ updated_input : Tool_input.t option;
6666+ }
6767+ (** Output from PreToolUse hooks. *)
12368124124- val transcript_path : t -> string
125125- val tool_name : t -> string
126126- val tool_input : t -> Jsont.json
127127- val unknown : t -> Unknown.t
128128- val input_jsont : input Jsont.t
6969+ (** {2 Response Builders} *)
12970130130- type permission_decision = [ `Allow | `Deny | `Ask ]
131131- (** Permission decision for tool usage *)
7171+ val allow : ?reason:string -> ?updated_input:Tool_input.t -> unit -> output
7272+ (** [allow ?reason ?updated_input ()] creates an allow response.
7373+ @param reason Optional explanation for allowing
7474+ @param updated_input Optional modified tool input *)
13275133133- val permission_decision_jsont : permission_decision Jsont.t
7676+ val deny : ?reason:string -> unit -> output
7777+ (** [deny ?reason ()] creates a deny response.
7878+ @param reason Optional explanation for denying *)
13479135135- type output = {
136136- permission_decision : permission_decision option;
137137- permission_decision_reason : string option;
138138- updated_input : Jsont.json option;
139139- unknown : Unknown.t;
140140- }
141141- (** Typed output for PreToolUse hooks *)
8080+ val ask : ?reason:string -> unit -> output
8181+ (** [ask ?reason ()] creates an ask response to prompt the user.
8282+ @param reason Optional explanation for asking *)
14283143143- val output_jsont : output Jsont.t
8484+ val continue : unit -> output
8585+ (** [continue ()] creates a continue response with no decision. *)
8686+8787+ (** {2 Callback Type} *)
8888+8989+ type callback = input -> output
9090+ (** Callback function type for PreToolUse hooks. *)
14491145145- val allow :
146146- ?reason:string ->
147147- ?updated_input:Jsont.json ->
148148- ?unknown:Unknown.t ->
149149- unit ->
150150- output
151151- (** {2 Response Builders} *)
9292+ (** {2 Conversion Functions} *)
15293153153- val deny : ?reason:string -> ?unknown:Unknown.t -> unit -> output
154154- val ask : ?reason:string -> ?unknown:Unknown.t -> unit -> output
155155- val continue : ?unknown:Unknown.t -> unit -> output
9494+ val input_of_proto : Proto.Hooks.PreToolUse.Input.t -> input
9595+ (** [input_of_proto proto] converts wire format input to typed input. *)
15696157157- val output_to_json : output -> Jsont.json
158158- (** Convert output to JSON for hook_specific_output *)
9797+ val output_to_proto : output -> Proto.Hooks.PreToolUse.Output.t
9898+ (** [output_to_proto output] converts typed output to wire format. *)
15999end
160100161101(** PostToolUse hook - fires after tool execution *)
162102module PostToolUse : sig
103103+ (** {2 Input} *)
104104+163105 type input = {
164106 session_id : string;
165107 transcript_path : string;
166108 tool_name : string;
167167- tool_input : Jsont.json;
168168- tool_response : Jsont.json;
169169- unknown : Unknown.t;
109109+ tool_input : Tool_input.t;
110110+ tool_response : Jsont.json; (* Response varies by tool *)
170111 }
112112+ (** Input provided to PostToolUse hooks.
113113+ Note: [tool_response] remains as {!Jsont.json} since response schemas
114114+ vary by tool. *)
171115172172- type t = input
173173-174174- val of_json : Jsont.json -> t
175175- val session_id : t -> string
176176- val transcript_path : t -> string
177177- val tool_name : t -> string
178178- val tool_input : t -> Jsont.json
179179- val tool_response : t -> Jsont.json
180180- val unknown : t -> Unknown.t
181181- val input_jsont : input Jsont.t
116116+ (** {2 Output} *)
182117183118 type output = {
184184- decision : decision option;
119119+ block : bool;
185120 reason : string option;
186121 additional_context : string option;
187187- unknown : Unknown.t;
188122 }
123123+ (** Output from PostToolUse hooks. *)
189124190190- val output_jsont : output Jsont.t
125125+ (** {2 Response Builders} *)
191126192192- val continue :
193193- ?additional_context:string -> ?unknown:Unknown.t -> unit -> output
127127+ val continue : ?additional_context:string -> unit -> output
128128+ (** [continue ?additional_context ()] creates a continue response.
129129+ @param additional_context Optional context to add to the transcript *)
194130195131 val block :
196196- ?reason:string ->
197197- ?additional_context:string ->
198198- ?unknown:Unknown.t ->
199199- unit ->
200200- output
132132+ ?reason:string -> ?additional_context:string -> unit -> output
133133+ (** [block ?reason ?additional_context ()] creates a block response.
134134+ @param reason Optional explanation for blocking
135135+ @param additional_context Optional context to add to the transcript *)
136136+137137+ (** {2 Callback Type} *)
201138202202- val output_to_json : output -> Jsont.json
139139+ type callback = input -> output
140140+ (** Callback function type for PostToolUse hooks. *)
141141+142142+ (** {2 Conversion Functions} *)
143143+144144+ val input_of_proto : Proto.Hooks.PostToolUse.Input.t -> input
145145+ (** [input_of_proto proto] converts wire format input to typed input. *)
146146+147147+ val output_to_proto : output -> Proto.Hooks.PostToolUse.Output.t
148148+ (** [output_to_proto output] converts typed output to wire format. *)
203149end
204150205151(** UserPromptSubmit hook - fires when user submits a prompt *)
206152module UserPromptSubmit : sig
153153+ (** {2 Input} *)
154154+207155 type input = {
208156 session_id : string;
209157 transcript_path : string;
210158 prompt : string;
211211- unknown : Unknown.t;
212159 }
213213-214214- type t = input
160160+ (** Input provided to UserPromptSubmit hooks. *)
215161216216- val of_json : Jsont.json -> t
217217- val session_id : t -> string
218218- val transcript_path : t -> string
219219- val prompt : t -> string
220220- val unknown : t -> Unknown.t
221221- val input_jsont : input Jsont.t
162162+ (** {2 Output} *)
222163223164 type output = {
224224- decision : decision option;
165165+ block : bool;
225166 reason : string option;
226167 additional_context : string option;
227227- unknown : Unknown.t;
228168 }
169169+ (** Output from UserPromptSubmit hooks. *)
229170230230- val output_jsont : output Jsont.t
171171+ (** {2 Response Builders} *)
172172+173173+ val continue : ?additional_context:string -> unit -> output
174174+ (** [continue ?additional_context ()] creates a continue response.
175175+ @param additional_context Optional context to add to the transcript *)
231176232232- val continue :
233233- ?additional_context:string -> ?unknown:Unknown.t -> unit -> output
177177+ val block : ?reason:string -> unit -> output
178178+ (** [block ?reason ()] creates a block response.
179179+ @param reason Optional explanation for blocking *)
234180235235- val block : ?reason:string -> ?unknown:Unknown.t -> unit -> output
236236- val output_to_json : output -> Jsont.json
181181+ (** {2 Callback Type} *)
182182+183183+ type callback = input -> output
184184+ (** Callback function type for UserPromptSubmit hooks. *)
185185+186186+ (** {2 Conversion Functions} *)
187187+188188+ val input_of_proto : Proto.Hooks.UserPromptSubmit.Input.t -> input
189189+ (** [input_of_proto proto] converts wire format input to typed input. *)
190190+191191+ val output_to_proto : output -> Proto.Hooks.UserPromptSubmit.Output.t
192192+ (** [output_to_proto output] converts typed output to wire format. *)
237193end
238194239195(** Stop hook - fires when conversation stops *)
240196module Stop : sig
197197+ (** {2 Input} *)
198198+241199 type input = {
242200 session_id : string;
243201 transcript_path : string;
244202 stop_hook_active : bool;
245245- unknown : Unknown.t;
246203 }
204204+ (** Input provided to Stop hooks. *)
247205248248- type t = input
249249-250250- val of_json : Jsont.json -> t
251251- val session_id : t -> string
252252- val transcript_path : t -> string
253253- val stop_hook_active : t -> bool
254254- val unknown : t -> Unknown.t
255255- val input_jsont : input Jsont.t
206206+ (** {2 Output} *)
256207257208 type output = {
258258- decision : decision option;
209209+ block : bool;
259210 reason : string option;
260260- unknown : Unknown.t;
261211 }
212212+ (** Output from Stop hooks. *)
213213+214214+ (** {2 Response Builders} *)
215215+216216+ val continue : unit -> output
217217+ (** [continue ()] creates a continue response. *)
262218263263- val output_jsont : output Jsont.t
264264- val continue : ?unknown:Unknown.t -> unit -> output
265265- val block : ?reason:string -> ?unknown:Unknown.t -> unit -> output
266266- val output_to_json : output -> Jsont.json
219219+ val block : ?reason:string -> unit -> output
220220+ (** [block ?reason ()] creates a block response.
221221+ @param reason Optional explanation for blocking *)
222222+223223+ (** {2 Callback Type} *)
224224+225225+ type callback = input -> output
226226+ (** Callback function type for Stop hooks. *)
227227+228228+ (** {2 Conversion Functions} *)
229229+230230+ val input_of_proto : Proto.Hooks.Stop.Input.t -> input
231231+ (** [input_of_proto proto] converts wire format input to typed input. *)
232232+233233+ val output_to_proto : output -> Proto.Hooks.Stop.Output.t
234234+ (** [output_to_proto output] converts typed output to wire format. *)
267235end
268236269237(** SubagentStop hook - fires when a subagent stops *)
270238module SubagentStop : sig
239239+ (** {2 Input} *)
240240+271241 type input = Stop.input
272272- type t = input
242242+ (** Same structure as Stop.input *)
243243+244244+ (** {2 Output} *)
245245+273246 type output = Stop.output
247247+ (** Same structure as Stop.output *)
274248275275- val of_json : Jsont.json -> t
276276- val session_id : t -> string
277277- val transcript_path : t -> string
278278- val stop_hook_active : t -> bool
279279- val unknown : t -> Unknown.t
280280- val input_jsont : input Jsont.t
281281- val output_jsont : output Jsont.t
282282- val continue : ?unknown:Unknown.t -> unit -> output
283283- val block : ?reason:string -> ?unknown:Unknown.t -> unit -> output
284284- val output_to_json : output -> Jsont.json
249249+ (** {2 Response Builders} *)
250250+251251+ val continue : unit -> output
252252+ (** [continue ()] creates a continue response. *)
253253+254254+ val block : ?reason:string -> unit -> output
255255+ (** [block ?reason ()] creates a block response.
256256+ @param reason Optional explanation for blocking *)
257257+258258+ (** {2 Callback Type} *)
259259+260260+ type callback = input -> output
261261+ (** Callback function type for SubagentStop hooks. *)
262262+263263+ (** {2 Conversion Functions} *)
264264+265265+ val input_of_proto : Proto.Hooks.SubagentStop.Input.t -> input
266266+ (** [input_of_proto proto] converts wire format input to typed input. *)
267267+268268+ val output_to_proto : output -> Proto.Hooks.SubagentStop.Output.t
269269+ (** [output_to_proto output] converts typed output to wire format. *)
285270end
286271287272(** PreCompact hook - fires before message compaction *)
288273module PreCompact : sig
274274+ (** {2 Input} *)
275275+289276 type input = {
290277 session_id : string;
291278 transcript_path : string;
292292- unknown : Unknown.t;
293279 }
280280+ (** Input provided to PreCompact hooks. *)
294281295295- type t = input
296296- type output = unit
282282+ (** {2 Callback Type} *)
297283298298- val of_json : Jsont.json -> t
299299- val session_id : t -> string
300300- val transcript_path : t -> string
301301- val unknown : t -> Unknown.t
302302- val input_jsont : input Jsont.t
303303- val continue : unit -> output
304304- val output_to_json : output -> Jsont.json
305305-end
284284+ type callback = input -> unit
285285+ (** Callback function type for PreCompact hooks.
286286+ PreCompact hooks have no output - they are notification-only. *)
306287307307-(** {1 Callbacks} *)
288288+ (** {2 Conversion Functions} *)
308289309309-type callback =
310310- input:Jsont.json -> tool_use_id:string option -> context:Context.t -> result
311311-(** Generic callback function type.
290290+ val input_of_proto : Proto.Hooks.PreCompact.Input.t -> input
291291+ (** [input_of_proto proto] converts wire format input to typed input. *)
292292+end
312293313313- Callbacks receive:
314314- - [input]: Raw JSON input (parse with [PreToolUse.of_json], etc.)
315315- - [tool_use_id]: Optional tool use ID
316316- - [context]: Hook context
294294+(** {1 Hook Configuration} *)
317295318318- And return a generic [result] with optional hook-specific output. *)
296296+type t
297297+(** Hook configuration.
319298320320-(** {1 Matchers} *)
299299+ Hooks are configured using a builder pattern:
300300+ {[
301301+ Hooks.empty
302302+ |> Hooks.on_pre_tool_use ~pattern:"Bash" bash_handler
303303+ |> Hooks.on_post_tool_use post_handler
304304+ ]} *)
321305322322-type matcher = {
323323- matcher : string option;
324324- (** Pattern to match (e.g., "Bash" or "Write|Edit") *)
325325- callbacks : callback list; (** Callbacks to invoke on match *)
326326-}
327327-(** A matcher configuration *)
306306+val empty : t
307307+(** [empty] is an empty hook configuration with no callbacks. *)
328308329329-type config = (event * matcher list) list
330330-(** Hook configuration: map from events to matchers *)
309309+val on_pre_tool_use : ?pattern:string -> PreToolUse.callback -> t -> t
310310+(** [on_pre_tool_use ?pattern callback config] adds a PreToolUse hook.
311311+ @param pattern Optional regex pattern to match tool names (e.g., "Bash|Edit")
312312+ @param callback Function to invoke on matching events *)
331313332332-(** {1 Generic Result Builders} *)
314314+val on_post_tool_use : ?pattern:string -> PostToolUse.callback -> t -> t
315315+(** [on_post_tool_use ?pattern callback config] adds a PostToolUse hook.
316316+ @param pattern Optional regex pattern to match tool names
317317+ @param callback Function to invoke on matching events *)
333318334334-val continue :
335335- ?system_message:string ->
336336- ?hook_specific_output:Jsont.json ->
337337- ?unknown:Unknown.t ->
338338- unit ->
339339- result
340340-(** [continue ?system_message ?hook_specific_output ?unknown ()] creates a
341341- continue result *)
319319+val on_user_prompt_submit : UserPromptSubmit.callback -> t -> t
320320+(** [on_user_prompt_submit callback config] adds a UserPromptSubmit hook.
321321+ @param callback Function to invoke on prompt submission *)
342322343343-val block :
344344- ?system_message:string ->
345345- ?hook_specific_output:Jsont.json ->
346346- ?unknown:Unknown.t ->
347347- unit ->
348348- result
349349-(** [block ?system_message ?hook_specific_output ?unknown ()] creates a block
350350- result *)
323323+val on_stop : Stop.callback -> t -> t
324324+(** [on_stop callback config] adds a Stop hook.
325325+ @param callback Function to invoke on conversation stop *)
351326352352-(** {1 Configuration Builders} *)
327327+val on_subagent_stop : SubagentStop.callback -> t -> t
328328+(** [on_subagent_stop callback config] adds a SubagentStop hook.
329329+ @param callback Function to invoke on subagent stop *)
353330354354-val matcher : ?pattern:string -> callback list -> matcher
355355-(** [matcher ?pattern callbacks] creates a matcher *)
331331+val on_pre_compact : PreCompact.callback -> t -> t
332332+(** [on_pre_compact callback config] adds a PreCompact hook.
333333+ @param callback Function to invoke before message compaction *)
356334357357-val empty : config
358358-(** Empty hooks configuration *)
335335+(** {1 Internal - for client use} *)
359336360360-val add : event -> matcher list -> config -> config
361361-(** [add event matchers config] adds matchers for an event *)
337337+val get_callbacks :
338338+ t ->
339339+ (Proto.Hooks.event * (string option * (Jsont.json -> Proto.Hooks.result))
340340+ list)
341341+ list
342342+(** [get_callbacks config] returns hook configuration in format suitable for
343343+ registration with the CLI.
362344363363-(** {1 JSON Serialization} *)
345345+ This function converts typed callbacks into wire format handlers that:
346346+ - Parse JSON input using Proto.Hooks types
347347+ - Convert to typed input using input_of_proto
348348+ - Invoke the user's typed callback
349349+ - Convert output back to wire format using output_to_proto
364350365365-val result_to_json : result -> Jsont.json
366366-val config_to_protocol_format : config -> Jsont.json
351351+ This is an internal function used by {!Client} - you should not need to
352352+ call it directly. *)
+6-6
lib/incoming.ml
···3030 ~dec:(fun v -> Control_response v)
3131 in
3232 let case_user =
3333- Jsont.Object.Case.map "user" Message.User.incoming_jsont
3434- ~dec:(fun v -> Message (Message.User v))
3333+ Jsont.Object.Case.map "user" Message.User.incoming_jsont ~dec:(fun v ->
3434+ Message (Message.User v))
3535 in
3636 let case_assistant =
3737 Jsont.Object.Case.map "assistant" Message.Assistant.incoming_jsont
3838 ~dec:(fun v -> Message (Message.Assistant v))
3939 in
4040 let case_system =
4141- Jsont.Object.Case.map "system" Message.System.jsont
4242- ~dec:(fun v -> Message (Message.System v))
4141+ Jsont.Object.Case.map "system" Message.System.jsont ~dec:(fun v ->
4242+ Message (Message.System v))
4343 in
4444 let case_result =
4545- Jsont.Object.Case.map "result" Message.Result.jsont
4646- ~dec:(fun v -> Message (Message.Result v))
4545+ Jsont.Object.Case.map "result" Message.Result.jsont ~dec:(fun v ->
4646+ Message (Message.Result v))
4747 in
4848 let enc_case = function
4949 | Control_request v -> Jsont.Object.Case.value case_control_request v
+102-576
lib/message.ml
···33module Log = (val Logs.src_log src : Logs.LOG)
4455module User = struct
66- type content = String of string | Blocks of Content_block.t list
77- type t = { content : content; unknown : Unknown.t }
66+ type t = Proto.Message.User.t
8799- let create_string s = { content = String s; unknown = Unknown.empty }
88+ let of_string s = Proto.Message.User.create_string s
99+ let of_blocks blocks = Proto.Message.User.create_blocks (List.map Content_block.to_proto blocks)
10101111- let create_blocks blocks =
1212- { content = Blocks blocks; unknown = Unknown.empty }
1111+ let with_tool_result ~tool_use_id ~content ?is_error () =
1212+ Proto.Message.User.create_with_tool_result ~tool_use_id ~content ?is_error ()
13131414- let create_with_tool_result ~tool_use_id ~content ?is_error () =
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 }
1414+ let as_text t =
1515+ match Proto.Message.User.content t with
1616+ | Proto.Message.User.String s -> Some s
1717+ | Proto.Message.User.Blocks _ -> None
19182020- let create_mixed ~text ~tool_results =
2121- let blocks =
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
3030- in
3131- text_blocks @ tool_blocks
3232- in
3333- { content = Blocks blocks; unknown = Unknown.empty }
3434-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
3939-4040- let get_blocks t =
4141- match t.content with
4242- | String s -> [ Content_block.text s ]
4343- | Blocks blocks -> blocks
4444-4545- (* Decode content from json value *)
4646- let decode_content json =
4747- match json with
4848- | Jsont.String (s, _) -> String s
4949- | Jsont.Array (items, _) ->
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
5757- Blocks blocks
5858- | _ -> failwith "Content must be string or array"
5959-6060- (* Encode content to json value *)
6161- let encode_content = function
6262- | String s -> Jsont.String (s, Jsont.Meta.none)
6363- | Blocks blocks ->
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
7171- Jsont.Array (jsons, Jsont.Meta.none)
7272-7373- let jsont : t Jsont.t =
7474- Jsont.Object.map ~kind:"User" (fun json_content unknown ->
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))
7979- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
8080- |> Jsont.Object.finish
8181-8282- (** Wire-format codec for outgoing user messages.
8383- Format: {"type": "user", "message": {"role": "user", "content": ...}} *)
8484- module Wire = struct
8585- type inner = { role : string; content : Jsont.json }
8686- type outer = { type_ : string; message : inner }
1919+ let blocks t =
2020+ match Proto.Message.User.content t with
2121+ | Proto.Message.User.String s -> [ Content_block.text s ]
2222+ | Proto.Message.User.Blocks bs -> List.map Content_block.of_proto bs
87238888- let inner_jsont : inner Jsont.t =
8989- let make role content = { role; content } in
9090- Jsont.Object.map ~kind:"UserMessageInner" make
9191- |> Jsont.Object.mem "role" Jsont.string ~enc:(fun r -> r.role)
9292- |> Jsont.Object.mem "content" Jsont.json ~enc:(fun r -> r.content)
9393- |> Jsont.Object.finish
2424+ let of_proto proto = proto
2525+ let to_proto t = t
94269595- let outer_jsont : outer Jsont.t =
9696- let make type_ message = { type_; message } in
9797- Jsont.Object.map ~kind:"UserMessageOuter" make
9898- |> Jsont.Object.mem "type" Jsont.string ~enc:(fun r -> r.type_)
9999- |> Jsont.Object.mem "message" inner_jsont ~enc:(fun r -> r.message)
100100- |> Jsont.Object.finish
101101- end
2727+ (* Internal wire format functions *)
2828+ let incoming_jsont = Proto.Message.User.incoming_jsont
1022910330 let to_json t =
104104- let content_json = encode_content t.content in
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: "
110110-111111- (* Jsont codec for parsing incoming user messages from CLI *)
112112- let incoming_jsont : t Jsont.t =
113113- let message_jsont =
114114- Jsont.Object.map ~kind:"UserMessage" (fun json_content ->
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))
119119- |> Jsont.Object.finish
120120- in
121121- Jsont.Object.map ~kind:"UserEnvelope" Fun.id
122122- |> Jsont.Object.mem "message" message_jsont ~enc:Fun.id
123123- |> Jsont.Object.finish
124124-125125- let of_json json =
126126- Jsont.Json.decode incoming_jsont json |> Err.get_ok' ~msg:"User.of_json: "
3131+ match Jsont.Json.encode Proto.Message.User.jsont t with
3232+ | Ok json -> json
3333+ | Error e -> invalid_arg ("User.to_json: " ^ e)
12734end
1283512936module Assistant = struct
130130- type error =
131131- [ `Authentication_failed
132132- | `Billing_error
133133- | `Rate_limit
134134- | `Invalid_request
135135- | `Server_error
136136- | `Unknown ]
3737+ type error = Proto.Message.Assistant.error
13738138138- let error_to_string = function
139139- | `Authentication_failed -> "authentication_failed"
140140- | `Billing_error -> "billing_error"
141141- | `Rate_limit -> "rate_limit"
142142- | `Invalid_request -> "invalid_request"
143143- | `Server_error -> "server_error"
144144- | `Unknown -> "unknown"
3939+ type t = Proto.Message.Assistant.t
14540146146- let error_of_string = function
147147- | "authentication_failed" -> `Authentication_failed
148148- | "billing_error" -> `Billing_error
149149- | "rate_limit" -> `Rate_limit
150150- | "invalid_request" -> `Invalid_request
151151- | "server_error" -> `Server_error
152152- | "unknown" | _ -> `Unknown
4141+ let content t = List.map Content_block.of_proto (Proto.Message.Assistant.content t)
4242+ let model t = Proto.Message.Assistant.model t
4343+ let error t = Proto.Message.Assistant.error t
15344154154- let error_jsont : error Jsont.t =
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- ]
164164-165165- type t = {
166166- content : Content_block.t list;
167167- model : string;
168168- error : error option;
169169- unknown : Unknown.t;
170170- }
171171-172172- let create ~content ~model ?error () =
173173- { content; model; error; unknown = Unknown.empty }
174174-175175- let make content model error unknown = { content; model; error; unknown }
176176- let content t = t.content
177177- let model t = t.model
178178- let error t = t.error
179179- let unknown t = t.unknown
180180-181181- let get_text_blocks t =
4545+ let text_blocks t =
18246 List.filter_map
18347 (function
18448 | Content_block.Text text -> Some (Content_block.Text.text text)
18549 | _ -> None)
186186- t.content
5050+ (content t)
18751188188- let get_tool_uses t =
5252+ let tool_uses t =
18953 List.filter_map
19054 (function Content_block.Tool_use tool -> Some tool | _ -> None)
191191- t.content
5555+ (content t)
19256193193- let get_thinking t =
5757+ let thinking_blocks t =
19458 List.filter_map
19559 (function Content_block.Thinking thinking -> Some thinking | _ -> None)
196196- t.content
6060+ (content t)
1976119862 let has_tool_use t =
199199- List.exists
200200- (function Content_block.Tool_use _ -> true | _ -> false)
201201- t.content
6363+ List.exists (function Content_block.Tool_use _ -> true | _ -> false) (content t)
20264203203- let combined_text t = String.concat "\n" (get_text_blocks t)
6565+ let combined_text t = String.concat "\n" (text_blocks t)
20466205205- let jsont : t Jsont.t =
206206- Jsont.Object.map ~kind:"Assistant" make
207207- |> Jsont.Object.mem "content" (Jsont.list Content_block.jsont) ~enc:content
208208- |> Jsont.Object.mem "model" Jsont.string ~enc:model
209209- |> Jsont.Object.opt_mem "error" error_jsont ~enc:error
210210- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
211211- |> Jsont.Object.finish
6767+ let of_proto proto = proto
6868+ let to_proto t = t
21269213213- let encode_content_blocks blocks =
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)
222222-223223- (** Wire-format codec for outgoing assistant messages. *)
224224- module Wire = struct
225225- type inner = {
226226- wire_content : Jsont.json;
227227- wire_model : string;
228228- wire_error : string option;
229229- }
230230-231231- type outer = { wire_type : string; wire_message : inner }
232232-233233- let inner_jsont : inner Jsont.t =
234234- let make wire_content wire_model wire_error =
235235- { wire_content; wire_model; wire_error }
236236- in
237237- Jsont.Object.map ~kind:"AssistantMessageInner" make
238238- |> Jsont.Object.mem "content" Jsont.json ~enc:(fun r -> r.wire_content)
239239- |> Jsont.Object.mem "model" Jsont.string ~enc:(fun r -> r.wire_model)
240240- |> Jsont.Object.opt_mem "error" Jsont.string ~enc:(fun r -> r.wire_error)
241241- |> Jsont.Object.finish
242242-243243- let outer_jsont : outer Jsont.t =
244244- let make wire_type wire_message = { wire_type; wire_message } in
245245- Jsont.Object.map ~kind:"AssistantMessageOuter" make
246246- |> Jsont.Object.mem "type" Jsont.string ~enc:(fun r -> r.wire_type)
247247- |> Jsont.Object.mem "message" inner_jsont ~enc:(fun r -> r.wire_message)
248248- |> Jsont.Object.finish
249249- end
7070+ (* Internal wire format functions *)
7171+ let incoming_jsont = Proto.Message.Assistant.incoming_jsont
2507225173 let to_json t =
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: "
266266-267267- (* Jsont codec for parsing incoming assistant messages from CLI *)
268268- let incoming_jsont : t Jsont.t =
269269- Jsont.Object.map ~kind:"AssistantEnvelope" Fun.id
270270- |> Jsont.Object.mem "message" jsont ~enc:Fun.id
271271- |> Jsont.Object.finish
272272-273273- let of_json json =
274274- Jsont.Json.decode incoming_jsont json
275275- |> Err.get_ok' ~msg:"Assistant.of_json: "
7474+ match Jsont.Json.encode Proto.Message.Assistant.jsont t with
7575+ | Ok json -> json
7676+ | Error e -> invalid_arg ("Assistant.to_json: " ^ e)
27677end
2777827879module System = struct
279279- (** System messages as a discriminated union on "subtype" field *)
280280-281281- type init = {
282282- session_id : string option;
283283- model : string option;
284284- cwd : string option;
285285- unknown : Unknown.t;
286286- }
287287-288288- type error = { error : string; unknown : Unknown.t }
289289- type t = Init of init | Error of error
290290-291291- (* Accessors *)
292292- let session_id = function Init i -> i.session_id | _ -> None
293293- let model = function Init i -> i.model | _ -> None
294294- let cwd = function Init i -> i.cwd | _ -> None
295295- let error_msg = function Error e -> Some e.error | _ -> None
296296- let subtype = function Init _ -> "init" | Error _ -> "error"
297297- let unknown = function Init i -> i.unknown | Error e -> e.unknown
298298-299299- (* Constructors *)
300300- let init ?session_id ?model ?cwd () =
301301- Init { session_id; model; cwd; unknown = Unknown.empty }
8080+ type t = Proto.Message.System.t
30281303303- let error ~error = Error { error; unknown = Unknown.empty }
304304-305305- (* Individual record codecs *)
306306- let init_jsont : init Jsont.t =
307307- let make session_id model cwd unknown : init =
308308- { session_id; model; cwd; unknown }
309309- in
310310- Jsont.Object.map ~kind:"SystemInit" make
311311- |> Jsont.Object.opt_mem "session_id" Jsont.string ~enc:(fun (r : init) ->
312312- r.session_id)
313313- |> Jsont.Object.opt_mem "model" Jsont.string ~enc:(fun (r : init) ->
314314- r.model)
315315- |> Jsont.Object.opt_mem "cwd" Jsont.string ~enc:(fun (r : init) -> r.cwd)
316316- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : init) ->
317317- r.unknown)
318318- |> Jsont.Object.finish
8282+ let is_init = function Proto.Message.System.Init _ -> true | _ -> false
8383+ let is_error = function Proto.Message.System.Error _ -> true | _ -> false
8484+ let session_id = Proto.Message.System.session_id
8585+ let model = Proto.Message.System.model
8686+ let cwd = Proto.Message.System.cwd
8787+ let error_message = Proto.Message.System.error_msg
31988320320- let error_jsont : error Jsont.t =
321321- let make err unknown : error = { error = err; unknown } in
322322- Jsont.Object.map ~kind:"SystemError" make
323323- |> Jsont.Object.mem "error" Jsont.string ~enc:(fun (r : error) -> r.error)
324324- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : error) ->
325325- r.unknown)
326326- |> Jsont.Object.finish
8989+ let of_proto proto = proto
9090+ let to_proto t = t
32791328328- (* Main codec using case_mem for "subtype" discriminator *)
329329- let jsont : t Jsont.t =
330330- let case_init =
331331- Jsont.Object.Case.map "init" init_jsont ~dec:(fun v -> Init v)
332332- in
333333- let case_error =
334334- Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v)
335335- in
336336- let enc_case = function
337337- | Init v -> Jsont.Object.Case.value case_init v
338338- | Error v -> Jsont.Object.Case.value case_error v
339339- in
340340- let cases = Jsont.Object.Case.[ make case_init; make case_error ] in
341341- Jsont.Object.map ~kind:"System" Fun.id
342342- |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
343343- ~tag_to_string:Fun.id ~tag_compare:String.compare
344344- |> Jsont.Object.finish
9292+ (* Internal wire format functions *)
9393+ let jsont = Proto.Message.System.jsont
3459434695 let to_json t =
347347- Jsont.Json.encode jsont t |> Err.get_ok ~msg:"System.to_json: "
348348-349349- let of_json json =
350350- Jsont.Json.decode jsont json |> Err.get_ok' ~msg:"System.of_json: "
9696+ match Jsont.Json.encode Proto.Message.System.jsont t with
9797+ | Ok json -> json
9898+ | Error e -> invalid_arg ("System.to_json: " ^ e)
35199end
352100353101module Result = struct
354102 module Usage = struct
355355- type t = {
356356- input_tokens : int option;
357357- output_tokens : int option;
358358- total_tokens : int option;
359359- cache_creation_input_tokens : int option;
360360- cache_read_input_tokens : int option;
361361- unknown : Unknown.t;
362362- }
103103+ type t = Proto.Message.Result.Usage.t
363104364364- let make input_tokens output_tokens total_tokens cache_creation_input_tokens
365365- cache_read_input_tokens unknown =
366366- {
367367- input_tokens;
368368- output_tokens;
369369- total_tokens;
370370- cache_creation_input_tokens;
371371- cache_read_input_tokens;
372372- unknown;
373373- }
105105+ let input_tokens = Proto.Message.Result.Usage.input_tokens
106106+ let output_tokens = Proto.Message.Result.Usage.output_tokens
107107+ let total_tokens = Proto.Message.Result.Usage.total_tokens
108108+ let cache_creation_input_tokens = Proto.Message.Result.Usage.cache_creation_input_tokens
109109+ let cache_read_input_tokens = Proto.Message.Result.Usage.cache_read_input_tokens
374110375375- let create ?input_tokens ?output_tokens ?total_tokens
376376- ?cache_creation_input_tokens ?cache_read_input_tokens () =
377377- {
378378- input_tokens;
379379- output_tokens;
380380- total_tokens;
381381- cache_creation_input_tokens;
382382- cache_read_input_tokens;
383383- unknown = Unknown.empty;
384384- }
385385-386386- let input_tokens t = t.input_tokens
387387- let output_tokens t = t.output_tokens
388388- let total_tokens t = t.total_tokens
389389- let cache_creation_input_tokens t = t.cache_creation_input_tokens
390390- let cache_read_input_tokens t = t.cache_read_input_tokens
391391- let unknown t = t.unknown
392392-393393- let jsont : t Jsont.t =
394394- Jsont.Object.map ~kind:"Usage" make
395395- |> Jsont.Object.opt_mem "input_tokens" Jsont.int ~enc:input_tokens
396396- |> Jsont.Object.opt_mem "output_tokens" Jsont.int ~enc:output_tokens
397397- |> Jsont.Object.opt_mem "total_tokens" Jsont.int ~enc:total_tokens
398398- |> Jsont.Object.opt_mem "cache_creation_input_tokens" Jsont.int
399399- ~enc:cache_creation_input_tokens
400400- |> Jsont.Object.opt_mem "cache_read_input_tokens" Jsont.int
401401- ~enc:cache_read_input_tokens
402402- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
403403- |> Jsont.Object.finish
404404-405405- let effective_input_tokens t =
406406- match t.input_tokens with
407407- | None -> 0
408408- | Some input ->
409409- let cached = Option.value t.cache_read_input_tokens ~default:0 in
410410- max 0 (input - cached)
411411-412412- let total_cost_estimate t ~input_price ~output_price =
413413- match (t.input_tokens, t.output_tokens) with
414414- | Some input, Some output ->
415415- let input_cost = float_of_int input *. input_price /. 1_000_000. in
416416- let output_cost = float_of_int output *. output_price /. 1_000_000. in
417417- Some (input_cost +. output_cost)
418418- | _ -> None
111111+ let of_proto proto = proto
419112 end
420113421421- type t = {
422422- subtype : string;
423423- duration_ms : int;
424424- duration_api_ms : int;
425425- is_error : bool;
426426- num_turns : int;
427427- session_id : string;
428428- total_cost_usd : float option;
429429- usage : Usage.t option;
430430- result : string option;
431431- structured_output : Jsont.json option;
432432- unknown : Unknown.t;
433433- }
114114+ type t = Proto.Message.Result.t
434115435435- let create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
436436- ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
437437- {
438438- subtype;
439439- duration_ms;
440440- duration_api_ms;
441441- is_error;
442442- num_turns;
443443- session_id;
444444- total_cost_usd;
445445- usage;
446446- result;
447447- structured_output;
448448- unknown = Unknown.empty;
449449- }
116116+ let duration_ms = Proto.Message.Result.duration_ms
117117+ let duration_api_ms = Proto.Message.Result.duration_api_ms
118118+ let is_error = Proto.Message.Result.is_error
119119+ let num_turns = Proto.Message.Result.num_turns
120120+ let session_id = Proto.Message.Result.session_id
121121+ let total_cost_usd = Proto.Message.Result.total_cost_usd
450122451451- let make subtype duration_ms duration_api_ms is_error num_turns session_id
452452- total_cost_usd usage result structured_output unknown =
453453- {
454454- subtype;
455455- duration_ms;
456456- duration_api_ms;
457457- is_error;
458458- num_turns;
459459- session_id;
460460- total_cost_usd;
461461- usage;
462462- result;
463463- structured_output;
464464- unknown;
465465- }
123123+ let usage t = Option.map Usage.of_proto (Proto.Message.Result.usage t)
124124+ let result_text = Proto.Message.Result.result
125125+ let structured_output = Proto.Message.Result.structured_output
466126467467- let subtype t = t.subtype
468468- let duration_ms t = t.duration_ms
469469- let duration_api_ms t = t.duration_api_ms
470470- let is_error t = t.is_error
471471- let num_turns t = t.num_turns
472472- let session_id t = t.session_id
473473- let total_cost_usd t = t.total_cost_usd
474474- let usage t = t.usage
475475- let result t = t.result
476476- let structured_output t = t.structured_output
477477- let unknown t = t.unknown
127127+ let of_proto proto = proto
128128+ let to_proto t = t
478129479479- let jsont : t Jsont.t =
480480- Jsont.Object.map ~kind:"Result" make
481481- |> Jsont.Object.mem "subtype" Jsont.string ~enc:subtype
482482- |> Jsont.Object.mem "duration_ms" Jsont.int ~enc:duration_ms
483483- |> Jsont.Object.mem "duration_api_ms" Jsont.int ~enc:duration_api_ms
484484- |> Jsont.Object.mem "is_error" Jsont.bool ~enc:is_error
485485- |> Jsont.Object.mem "num_turns" Jsont.int ~enc:num_turns
486486- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
487487- |> Jsont.Object.opt_mem "total_cost_usd" Jsont.number ~enc:total_cost_usd
488488- |> Jsont.Object.opt_mem "usage" Usage.jsont ~enc:usage
489489- |> Jsont.Object.opt_mem "result" Jsont.string ~enc:result
490490- |> Jsont.Object.opt_mem "structured_output" Jsont.json
491491- ~enc:structured_output
492492- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
493493- |> Jsont.Object.finish
494494-495495- (** Wire-format codec for outgoing result messages (adds "type" field). *)
496496- module Wire = struct
497497- type wire = {
498498- type_ : string;
499499- subtype : string;
500500- duration_ms : int;
501501- duration_api_ms : int;
502502- is_error : bool;
503503- num_turns : int;
504504- session_id : string;
505505- total_cost_usd : float option;
506506- usage : Jsont.json option;
507507- result : string option;
508508- structured_output : Jsont.json option;
509509- }
510510-511511- let jsont : wire Jsont.t =
512512- let make type_ subtype duration_ms duration_api_ms is_error num_turns
513513- session_id total_cost_usd usage result structured_output =
514514- {
515515- type_;
516516- subtype;
517517- duration_ms;
518518- duration_api_ms;
519519- is_error;
520520- num_turns;
521521- session_id;
522522- total_cost_usd;
523523- usage;
524524- result;
525525- structured_output;
526526- }
527527- in
528528- Jsont.Object.map ~kind:"ResultWire" make
529529- |> Jsont.Object.mem "type" Jsont.string ~enc:(fun r -> r.type_)
530530- |> Jsont.Object.mem "subtype" Jsont.string ~enc:(fun r -> r.subtype)
531531- |> Jsont.Object.mem "duration_ms" Jsont.int ~enc:(fun r -> r.duration_ms)
532532- |> Jsont.Object.mem "duration_api_ms" Jsont.int ~enc:(fun r ->
533533- r.duration_api_ms)
534534- |> Jsont.Object.mem "is_error" Jsont.bool ~enc:(fun r -> r.is_error)
535535- |> Jsont.Object.mem "num_turns" Jsont.int ~enc:(fun r -> r.num_turns)
536536- |> Jsont.Object.mem "session_id" Jsont.string ~enc:(fun r -> r.session_id)
537537- |> Jsont.Object.opt_mem "total_cost_usd" Jsont.number ~enc:(fun r ->
538538- r.total_cost_usd)
539539- |> Jsont.Object.opt_mem "usage" Jsont.json ~enc:(fun r -> r.usage)
540540- |> Jsont.Object.opt_mem "result" Jsont.string ~enc:(fun r -> r.result)
541541- |> Jsont.Object.opt_mem "structured_output" Jsont.json ~enc:(fun r ->
542542- r.structured_output)
543543- |> Jsont.Object.finish
544544- end
130130+ (* Internal wire format functions *)
131131+ let jsont = Proto.Message.Result.jsont
545132546133 let to_json t =
547547- let usage_json =
548548- t.usage
549549- |> Option.map (fun u ->
550550- Jsont.Json.encode Usage.jsont u
551551- |> Err.get_ok ~msg:"Result.to_json: usage: ")
552552- in
553553- let wire =
554554- Wire.
555555- {
556556- type_ = "result";
557557- subtype = t.subtype;
558558- duration_ms = t.duration_ms;
559559- duration_api_ms = t.duration_api_ms;
560560- is_error = t.is_error;
561561- num_turns = t.num_turns;
562562- session_id = t.session_id;
563563- total_cost_usd = t.total_cost_usd;
564564- usage = usage_json;
565565- result = t.result;
566566- structured_output = t.structured_output;
567567- }
568568- in
569569- Jsont.Json.encode Wire.jsont wire |> Err.get_ok ~msg:"Result.to_json: "
570570-571571- let of_json json =
572572- Jsont.Json.decode jsont json |> Err.get_ok' ~msg:"Result.of_json: "
134134+ match Jsont.Json.encode Proto.Message.Result.jsont t with
135135+ | Ok json -> json
136136+ | Error e -> invalid_arg ("Result.to_json: " ^ e)
573137end
574138575139type t =
···578142 | System of System.t
579143 | Result of Result.t
580144581581-let user_string s = User (User.create_string s)
582582-let user_blocks blocks = User (User.create_blocks blocks)
583583-584584-let user_with_tool_result ~tool_use_id ~content ?is_error () =
585585- User (User.create_with_tool_result ~tool_use_id ~content ?is_error ())
586586-587587-let assistant ~content ~model ?error () =
588588- Assistant (Assistant.create ~content ~model ?error ())
589589-590590-let assistant_text ~text ~model ?error () =
591591- Assistant
592592- (Assistant.create ~content:[ Content_block.text text ] ~model ?error ())
593593-594594-let system_init ~session_id = System (System.init ~session_id ())
595595-let system_error ~error = System (System.error ~error)
596596-597597-let result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
598598- ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
599599- Result
600600- (Result.create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
601601- ~session_id ?total_cost_usd ?usage ?result ?structured_output ())
602602-603603-let to_json = function
604604- | User t -> User.to_json t
605605- | Assistant t -> Assistant.to_json t
606606- | System t -> System.to_json t
607607- | Result t -> Result.to_json t
608608-609609-(* Jsont codec for the main Message variant type.
610610- Uses case_mem for discriminated union based on "type" field. *)
611611-let jsont : t Jsont.t =
612612- let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
613613- let case_user = case_map "user" User.incoming_jsont (fun v -> User v) in
614614- let case_assistant =
615615- case_map "assistant" Assistant.incoming_jsont (fun v -> Assistant v)
616616- in
617617- let case_system = case_map "system" System.jsont (fun v -> System v) in
618618- let case_result = case_map "result" Result.jsont (fun v -> Result v) in
619619- let enc_case = function
620620- | User v -> Jsont.Object.Case.value case_user v
621621- | Assistant v -> Jsont.Object.Case.value case_assistant v
622622- | System v -> Jsont.Object.Case.value case_system v
623623- | Result v -> Jsont.Object.Case.value case_result v
624624- in
625625- let cases =
626626- Jsont.Object.Case.
627627- [
628628- make case_user; make case_assistant; make case_system; make case_result;
629629- ]
630630- in
631631- Jsont.Object.map ~kind:"Message" Fun.id
632632- |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
633633- ~tag_to_string:Fun.id ~tag_compare:String.compare
634634- |> Jsont.Object.finish
145145+let of_proto = function
146146+ | Proto.Message.User u -> User (User.of_proto u)
147147+ | Proto.Message.Assistant a -> Assistant (Assistant.of_proto a)
148148+ | Proto.Message.System s -> System (System.of_proto s)
149149+ | Proto.Message.Result r -> Result (Result.of_proto r)
635150636636-let of_json json =
637637- Jsont.Json.decode jsont json |> Err.get_ok' ~msg:"Message.of_json: "
151151+let to_proto = function
152152+ | User u -> Proto.Message.User (User.to_proto u)
153153+ | Assistant a -> Proto.Message.Assistant (Assistant.to_proto a)
154154+ | System s -> Proto.Message.System (System.to_proto s)
155155+ | Result r -> Proto.Message.Result (Result.to_proto r)
638156639157let is_user = function User _ -> true | _ -> false
640158let is_assistant = function Assistant _ -> true | _ -> false
···643161644162let is_error = function
645163 | Result r -> Result.is_error r
646646- | System (System.Error _) -> true
164164+ | System s -> System.is_error s
647165 | _ -> false
648166649167let extract_text = function
···653171 if text = "" then None else Some text
654172 | _ -> None
655173656656-let extract_tool_uses = function
657657- | Assistant a -> Assistant.get_tool_uses a
658658- | _ -> []
174174+let extract_tool_uses = function Assistant a -> Assistant.tool_uses a | _ -> []
659175660176let get_session_id = function
661177 | System s -> System.session_id s
662178 | Result r -> Some (Result.session_id r)
663179 | _ -> None
664180665665-let pp = Jsont.pp_value jsont ()
181181+(* Wire format conversion *)
182182+let to_json = function
183183+ | User u -> User.to_json u
184184+ | Assistant a -> Assistant.to_json a
185185+ | System s -> System.to_json s
186186+ | Result r -> Result.to_json r
187187+188188+(* Convenience constructors *)
189189+let user_string s = User (User.of_string s)
190190+let user_blocks blocks = User (User.of_blocks blocks)
191191+192192+let pp fmt t = Jsont.pp_value Proto.Message.jsont () fmt (to_proto t)
666193let log_received t = Log.info (fun m -> m "ā %a" pp t)
667194let log_sending t = Log.info (fun m -> m "ā %a" pp t)
668668-let log_error msg t = Log.err (fun m -> m "%s: %a" msg pp t)
+99-246
lib/message.mli
···11-(** Messages exchanged with Claude.
11+(** Messages exchanged with Claude. Opaque types.
2233- This module defines the various types of messages that can be sent to and
44- received from Claude, including user input, assistant responses, system
55- messages, and result metadata. *)
33+ This module provides opaque message types that wrap the proto types but hide
44+ the unknown fields and wire format details from the public API. *)
6576val src : Logs.Src.t
87(** The log source for message operations *)
···1211module User : sig
1312 (** Messages sent by the user. *)
14131515- (** The content of a user message. *)
1616- type content =
1717- | String of string (** Simple text message *)
1818- | Blocks of Content_block.t list
1919- (** Complex message with multiple content blocks *)
2020-2114 type t
2222- (** The type of user messages. *)
1515+ (** The type of user messages (opaque). *)
23162424- val jsont : t Jsont.t
2525- (** [jsont] is the Jsont codec for user messages. *)
1717+ val of_string : string -> t
1818+ (** [of_string s] creates a user message with simple text content. *)
26192727- val incoming_jsont : t Jsont.t
2828- (** [incoming_jsont] is the codec for parsing incoming user messages from CLI.
2929- This parses the envelope format with "message" wrapper. *)
2020+ val of_blocks : Content_block.t list -> t
2121+ (** [of_blocks blocks] creates a user message with content blocks. *)
30223131- val create_string : string -> t
3232- (** [create_string s] creates a user message with simple text content. *)
2323+ val with_tool_result :
2424+ tool_use_id:string -> content:string -> ?is_error:bool -> unit -> t
2525+ (** [with_tool_result ~tool_use_id ~content ?is_error ()] creates a user
2626+ message containing a tool result. *)
33273434- val create_blocks : Content_block.t list -> t
3535- (** [create_blocks blocks] creates a user message with content blocks. *)
2828+ val as_text : t -> string option
2929+ (** [as_text t] returns the text content if the message is a simple string,
3030+ None otherwise. *)
36313737- val create_with_tool_result :
3838- tool_use_id:string -> content:string -> ?is_error:bool -> unit -> t
3939- (** [create_with_tool_result ~tool_use_id ~content ?is_error ()] creates a
4040- user message containing a tool result. *)
3232+ val blocks : t -> Content_block.t list
3333+ (** [blocks t] returns the content blocks, or a single text block if it's a
3434+ string message. *)
41354242- val create_mixed :
4343- text:string option -> tool_results:(string * string * bool option) list -> t
4444- (** [create_mixed ?text ~tool_results] creates a user message with optional
4545- text and tool results. Each tool result is (tool_use_id, content,
4646- is_error). *)
3636+ (** {1 Internal - for lib use only} *)
47374848- val content : t -> content
4949- (** [content t] returns the content of the user message. *)
5050-5151- val unknown : t -> Unknown.t
5252- (** [unknown t] returns the unknown fields preserved from JSON. *)
3838+ val of_proto : Proto.Message.User.t -> t
3939+ (** [of_proto proto] wraps a proto user message. *)
53405454- val as_text : t -> string option
5555- (** [as_text t] returns the text content if the message is a simple string,
5656- None otherwise. *)
4141+ val to_proto : t -> Proto.Message.User.t
4242+ (** [to_proto t] extracts the proto user message. *)
57435858- val get_blocks : t -> Content_block.t list
5959- (** [get_blocks t] returns the content blocks, or a single text block if it's
6060- a string message. *)
4444+ val incoming_jsont : t Jsont.t
4545+ (** Internal codec for parsing incoming messages. *)
61466247 val to_json : t -> Jsont.json
6363- (** [to_json t] converts the user message to its JSON representation. *)
6464-6565- val of_json : Jsont.json -> t
6666- (** [of_json json] parses a user message from JSON.
6767- @raise Invalid_argument if the JSON is not a valid user message. *)
4848+ (** Internal conversion to JSON for wire format. *)
6849end
69507051(** {1 Assistant Messages} *)
···8162 | `Unknown (** Unknown error type *) ]
8263 (** The type of assistant message errors based on Python SDK error types. *)
83648484- val error_to_string : error -> string
8585- (** [error_to_string err] converts an error to its string representation. *)
8686-8787- val error_of_string : string -> error
8888- (** [error_of_string s] parses an error string. Unknown strings become
8989- [`Unknown]. *)
9090-9165 type t
9292- (** The type of assistant messages. *)
9393-9494- val jsont : t Jsont.t
9595- (** [jsont] is the Jsont codec for assistant messages. *)
9696-9797- val incoming_jsont : t Jsont.t
9898- (** [incoming_jsont] is the codec for parsing incoming assistant messages from
9999- CLI. This parses the envelope format with "message" wrapper. *)
100100-101101- val create :
102102- content:Content_block.t list -> model:string -> ?error:error -> unit -> t
103103- (** [create ~content ~model ?error ()] creates an assistant message.
104104- @param content List of content blocks in the response
105105- @param model The model identifier used for the response
106106- @param error Optional error that occurred during message generation *)
6666+ (** The type of assistant messages (opaque). *)
1076710868 val content : t -> Content_block.t list
10969 (** [content t] returns the content blocks of the assistant message. *)
···11575 (** [error t] returns the optional error that occurred during message
11676 generation. *)
11777118118- val unknown : t -> Unknown.t
119119- (** [unknown t] returns the unknown fields preserved from JSON. *)
7878+ (** {2 Convenience accessors} *)
12079121121- val get_text_blocks : t -> string list
122122- (** [get_text_blocks t] extracts all text content from the message. *)
8080+ val text_blocks : t -> string list
8181+ (** [text_blocks t] extracts all text content from the message. *)
8282+8383+ val tool_uses : t -> Content_block.Tool_use.t list
8484+ (** [tool_uses t] extracts all tool use blocks from the message. *)
12385124124- val get_tool_uses : t -> Content_block.Tool_use.t list
125125- (** [get_tool_uses t] extracts all tool use blocks from the message. *)
8686+ val thinking_blocks : t -> Content_block.Thinking.t list
8787+ (** [thinking_blocks t] extracts all thinking blocks from the message. *)
12688127127- val get_thinking : t -> Content_block.Thinking.t list
128128- (** [get_thinking t] extracts all thinking blocks from the message. *)
8989+ val combined_text : t -> string
9090+ (** [combined_text t] concatenates all text blocks into a single string. *)
1299113092 val has_tool_use : t -> bool
13193 (** [has_tool_use t] returns true if the message contains any tool use blocks.
13294 *)
13395134134- val combined_text : t -> string
135135- (** [combined_text t] concatenates all text blocks into a single string. *)
9696+ (** {1 Internal - for lib use only} *)
9797+9898+ val of_proto : Proto.Message.Assistant.t -> t
9999+ (** [of_proto proto] wraps a proto assistant message. *)
100100+101101+ val to_proto : t -> Proto.Message.Assistant.t
102102+ (** [to_proto t] extracts the proto assistant message. *)
103103+104104+ val incoming_jsont : t Jsont.t
105105+ (** Internal codec for parsing incoming messages. *)
136106137107 val to_json : t -> Jsont.json
138138- (** [to_json t] converts the assistant message to its JSON representation. *)
139139-140140- val of_json : Jsont.json -> t
141141- (** [of_json json] parses an assistant message from JSON.
142142- @raise Invalid_argument if the JSON is not a valid assistant message. *)
108108+ (** Internal conversion to JSON for wire format. *)
143109end
144110145111(** {1 System Messages} *)
146112147113module System : sig
148148- (** System control and status messages.
149149-150150- System messages use a discriminated union on the "subtype" field:
151151- - "init": Session initialization with session_id, model, cwd
152152- - "error": Error messages with error string *)
114114+ (** System control and status messages. *)
153115154154- type init = {
155155- session_id : string option;
156156- model : string option;
157157- cwd : string option;
158158- unknown : Unknown.t;
159159- }
160160- (** Init message fields. *)
116116+ type t
117117+ (** The type of system messages (opaque). *)
161118162162- type error = { error : string; unknown : Unknown.t }
163163- (** Error message fields. *)
119119+ val is_init : t -> bool
120120+ (** [is_init t] returns true if the message is an init message. *)
164121165165- type t = Init of init | Error of error
166166-167167- val jsont : t Jsont.t
168168- (** [jsont] is the Jsont codec for system messages. *)
169169-170170- (** {2 Constructors} *)
171171-172172- val init : ?session_id:string -> ?model:string -> ?cwd:string -> unit -> t
173173- (** [init ?session_id ?model ?cwd ()] creates an init message. *)
174174-175175- val error : error:string -> t
176176- (** [error ~error] creates an error message. *)
177177-178178- (** {2 Accessors} *)
122122+ val is_error : t -> bool
123123+ (** [is_error t] returns true if the message is an error message. *)
179124180125 val session_id : t -> string option
181126 (** [session_id t] returns session_id from Init, None otherwise. *)
···186131 val cwd : t -> string option
187132 (** [cwd t] returns cwd from Init, None otherwise. *)
188133189189- val error_msg : t -> string option
190190- (** [error_msg t] returns error from Error, None otherwise. *)
134134+ val error_message : t -> string option
135135+ (** [error_message t] returns error from Error, None otherwise. *)
191136192192- val subtype : t -> string
193193- (** [subtype t] returns the subtype string. *)
137137+ (** {1 Internal - for lib use only} *)
138138+139139+ val of_proto : Proto.Message.System.t -> t
140140+ (** [of_proto proto] wraps a proto system message. *)
194141195195- val unknown : t -> Unknown.t
196196- (** [unknown t] returns the unknown fields. *)
142142+ val to_proto : t -> Proto.Message.System.t
143143+ (** [to_proto t] extracts the proto system message. *)
197144198198- (** {2 Conversion} *)
145145+ val jsont : t Jsont.t
146146+ (** Internal codec for wire format. *)
199147200148 val to_json : t -> Jsont.json
201201- (** [to_json t] converts to JSON representation. *)
202202-203203- val of_json : Jsont.json -> t
204204- (** [of_json json] parses from JSON.
205205- @raise Invalid_argument if invalid. *)
149149+ (** Internal conversion to JSON for wire format. *)
206150end
207151208152(** {1 Result Messages} *)
···214158 (** Usage statistics for API calls. *)
215159216160 type t
217217- (** Type for usage statistics. *)
218218-219219- val jsont : t Jsont.t
220220- (** [jsont] is the Jsont codec for usage statistics. *)
221221-222222- val create :
223223- ?input_tokens:int ->
224224- ?output_tokens:int ->
225225- ?total_tokens:int ->
226226- ?cache_creation_input_tokens:int ->
227227- ?cache_read_input_tokens:int ->
228228- unit ->
229229- t
230230- (** [create ?input_tokens ?output_tokens ?total_tokens
231231- ?cache_creation_input_tokens ?cache_read_input_tokens ()] creates usage
232232- statistics. *)
161161+ (** Type for usage statistics (opaque). *)
233162234163 val input_tokens : t -> int option
235164 (** [input_tokens t] returns the number of input tokens used. *)
···246175 val cache_read_input_tokens : t -> int option
247176 (** [cache_read_input_tokens t] returns cache read input tokens. *)
248177249249- val unknown : t -> Unknown.t
250250- (** [unknown t] returns the unknown fields preserved from JSON. *)
178178+ (** {1 Internal - for lib use only} *)
251179252252- val effective_input_tokens : t -> int
253253- (** [effective_input_tokens t] returns input tokens minus cached tokens, or
254254- 0 if not available. *)
255255-256256- val total_cost_estimate :
257257- t -> input_price:float -> output_price:float -> float option
258258- (** [total_cost_estimate t ~input_price ~output_price] estimates the cost
259259- based on token prices per million tokens. Returns None if token counts
260260- are not available. *)
180180+ val of_proto : Proto.Message.Result.Usage.t -> t
181181+ (** [of_proto proto] wraps a proto usage object. *)
261182 end
262183263184 type t
264264- (** The type of result messages. *)
265265-266266- val jsont : t Jsont.t
267267- (** [jsont] is the Jsont codec for result messages. *)
268268-269269- val create :
270270- subtype:string ->
271271- duration_ms:int ->
272272- duration_api_ms:int ->
273273- is_error:bool ->
274274- num_turns:int ->
275275- session_id:string ->
276276- ?total_cost_usd:float ->
277277- ?usage:Usage.t ->
278278- ?result:string ->
279279- ?structured_output:Jsont.json ->
280280- unit ->
281281- t
282282- (** [create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
283283- ~session_id ?total_cost_usd ?usage ?result ()] creates a result message.
284284- @param subtype The subtype of the result
285285- @param duration_ms Total duration in milliseconds
286286- @param duration_api_ms API duration in milliseconds
287287- @param is_error Whether the result represents an error
288288- @param num_turns Number of conversation turns
289289- @param session_id Unique session identifier
290290- @param total_cost_usd Optional total cost in USD
291291- @param usage Optional usage statistics as JSON
292292- @param result Optional result string
293293- @param structured_output Optional structured JSON output from Claude *)
294294-295295- val subtype : t -> string
296296- (** [subtype t] returns the subtype of the result. *)
185185+ (** The type of result messages (opaque). *)
297186298187 val duration_ms : t -> int
299188 (** [duration_ms t] returns the total duration in milliseconds. *)
···316205 val usage : t -> Usage.t option
317206 (** [usage t] returns the optional usage statistics. *)
318207319319- val result : t -> string option
320320- (** [result t] returns the optional result string. *)
208208+ val result_text : t -> string option
209209+ (** [result_text t] returns the optional result string. *)
321210322211 val structured_output : t -> Jsont.json option
323212 (** [structured_output t] returns the optional structured JSON output. *)
324213325325- val unknown : t -> Unknown.t
326326- (** [unknown t] returns the unknown fields preserved from JSON. *)
214214+ (** {1 Internal - for lib use only} *)
327215328328- val to_json : t -> Jsont.json
329329- (** [to_json t] converts the result message to its JSON representation. *)
216216+ val of_proto : Proto.Message.Result.t -> t
217217+ (** [of_proto proto] wraps a proto result message. *)
218218+219219+ val to_proto : t -> Proto.Message.Result.t
220220+ (** [to_proto t] extracts the proto result message. *)
330221331331- val of_json : Jsont.json -> t
332332- (** [of_json json] parses a result message from JSON.
333333- @raise Invalid_argument if the JSON is not a valid result message. *)
222222+ val jsont : t Jsont.t
223223+ (** Internal codec for wire format. *)
224224+225225+ val to_json : t -> Jsont.json
226226+ (** Internal conversion to JSON for wire format. *)
334227end
335228336229(** {1 Message Union Type} *)
···343236 (** The type of messages, which can be user, assistant, system, or result.
344237 *)
345238346346-val jsont : t Jsont.t
347347-(** [jsont] is the Jsont codec for messages. *)
348348-349349-val user_string : string -> t
350350-(** [user_string s] creates a user message with text content. *)
351351-352352-val user_blocks : Content_block.t list -> t
353353-(** [user_blocks blocks] creates a user message with content blocks. *)
354354-355355-val user_with_tool_result :
356356- tool_use_id:string -> content:string -> ?is_error:bool -> unit -> t
357357-(** [user_with_tool_result ~tool_use_id ~content ?is_error ()] creates a user
358358- message containing a tool result. *)
359359-360360-val assistant :
361361- content:Content_block.t list ->
362362- model:string ->
363363- ?error:Assistant.error ->
364364- unit ->
365365- t
366366-(** [assistant ~content ~model ?error ()] creates an assistant message. *)
367367-368368-val assistant_text :
369369- text:string -> model:string -> ?error:Assistant.error -> unit -> t
370370-(** [assistant_text ~text ~model ?error ()] creates an assistant message with
371371- only text content. *)
372372-373373-val system_init : session_id:string -> t
374374-(** [system_init ~session_id] creates a system init message. *)
239239+val of_proto : Proto.Message.t -> t
240240+(** [of_proto proto] converts a proto message to a lib message. *)
375241376376-val system_error : error:string -> t
377377-(** [system_error ~error] creates a system error message. *)
242242+val to_proto : t -> Proto.Message.t
243243+(** [to_proto t] converts a lib message to a proto message. *)
378244379379-val result :
380380- subtype:string ->
381381- duration_ms:int ->
382382- duration_api_ms:int ->
383383- is_error:bool ->
384384- num_turns:int ->
385385- session_id:string ->
386386- ?total_cost_usd:float ->
387387- ?usage:Result.Usage.t ->
388388- ?result:string ->
389389- ?structured_output:Jsont.json ->
390390- unit ->
391391- t
392392-(** [result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
393393- ~session_id ?total_cost_usd ?usage ?result ()] creates a result message. *)
245245+(** {1 Internal - wire format conversion} *)
394246395247val to_json : t -> Jsont.json
396396-(** [to_json t] converts any message to its JSON representation. *)
248248+(** [to_json t] converts any message to its JSON wire format representation. *)
397249398398-val of_json : Jsont.json -> t
399399-(** [of_json json] parses a message from JSON.
400400- @raise Invalid_argument if the JSON is not a valid message. *)
250250+(** {1 Convenience Constructors} *)
251251+252252+val user_string : string -> t
253253+(** [user_string s] creates a user message with text content. *)
401254402402-val pp : Format.formatter -> t -> unit
403403-(** [pp fmt t] pretty-prints any message. *)
255255+val user_blocks : Content_block.t list -> t
256256+(** [user_blocks blocks] creates a user message with content blocks. *)
404257405258(** {1 Message Analysis} *)
406259···431284432285(** {1 Logging} *)
433286287287+val pp : Format.formatter -> t -> unit
288288+(** [pp fmt t] pretty-prints any message. *)
289289+434290val log_received : t -> unit
435291(** [log_received t] logs that a message was received. *)
436292437293val log_sending : t -> unit
438294(** [log_sending t] logs that a message is being sent. *)
439439-440440-val log_error : string -> t -> unit
441441-(** [log_error msg t] logs an error with the given message and context. *)
+109-131
lib/options.ml
···11-let src = Logs.Src.create "claude.options" ~doc:"Claude configuration options"
11+let src = Logs.Src.create "claudeio.options" ~doc:"Claude configuration options"
2233module Log = (val Logs.src_log src : Logs.LOG)
44-55-type setting_source = User | Project | Local
6475type t = {
86 allowed_tools : string list;
···1210 append_system_prompt : string option;
1311 permission_mode : Permissions.Mode.t option;
1412 permission_callback : Permissions.callback option;
1515- model : Model.t option;
1313+ model : Proto.Model.t option;
1614 cwd : Eio.Fs.dir_ty Eio.Path.t option;
1715 env : (string * string) list;
1816 continue_conversation : bool;
···2321 add_dirs : string list;
2422 extra_args : (string * string option) list;
2523 debug_stderr : Eio.Flow.sink_ty Eio.Flow.sink option;
2626- hooks : Hooks.config option;
2424+ hooks : Hooks.t option;
2725 max_budget_usd : float option;
2828- fallback_model : Model.t option;
2929- setting_sources : setting_source list option;
2626+ fallback_model : Proto.Model.t option;
2727+ setting_sources : Proto.Options.setting_source list option;
3028 max_buffer_size : int option;
3129 user : string option;
3232- output_format : Structured_output.t option;
3333- unknown : Unknown.t;
3030+ output_format : Proto.Structured_output.t option;
3431}
35323633let default =
···4138 system_prompt = None;
4239 append_system_prompt = None;
4340 permission_mode = None;
4444- permission_callback = Some Permissions.default_allow_callback;
4141+ permission_callback = Some Permissions.default_allow;
4542 model = None;
4643 cwd = None;
4744 env = [];
···6057 max_buffer_size = None;
6158 user = None;
6259 output_format = None;
6363- unknown = Unknown.empty;
6460 }
65616666-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- }
101101-6262+(* Accessors *)
10263let allowed_tools t = t.allowed_tools
10364let disallowed_tools t = t.disallowed_tools
10465let max_thinking_tokens t = t.max_thinking_tokens
···12485let max_buffer_size t = t.max_buffer_size
12586let user t = t.user
12687let output_format t = t.output_format
127127-let unknown t = t.unknown
8888+8989+(* Builders *)
12890let with_allowed_tools tools t = { t with allowed_tools = tools }
12991let with_disallowed_tools tools t = { t with disallowed_tools = tools }
13092let with_max_thinking_tokens tokens t = { t with max_thinking_tokens = tokens }
···139101 { t with permission_callback = Some callback }
140102141103let with_model model t = { t with model = Some model }
142142-let with_model_string model t = { t with model = Some (Model.of_string model) }
143143-let with_cwd cwd t = { t with cwd = Some cwd }
104104+let with_cwd cwd t = { t with cwd = Some (cwd :> Eio.Fs.dir_ty Eio.Path.t) }
144105let with_env env t = { t with env }
145106146107let with_continue_conversation continue t =
···155116let with_settings path t = { t with settings = Some path }
156117let with_add_dirs dirs t = { t with add_dirs = dirs }
157118let with_extra_args args t = { t with extra_args = args }
158158-let with_debug_stderr sink t = { t with debug_stderr = Some sink }
119119+let with_debug_stderr sink t = { t with debug_stderr = Some (sink :> Eio.Flow.sink_ty Eio.Flow.sink) }
159120let with_hooks hooks t = { t with hooks = Some hooks }
160121let with_max_budget_usd budget t = { t with max_budget_usd = Some budget }
161122let with_fallback_model model t = { t with fallback_model = Some model }
162123163163-let with_fallback_model_string model t =
164164- { t with fallback_model = Some (Model.of_string model) }
124124+let with_no_settings t = { t with setting_sources = Some [] }
165125166166-let with_setting_sources sources t = { t with setting_sources = Some sources }
167167-let with_no_settings t = { t with setting_sources = Some [] }
168126let with_max_buffer_size size t = { t with max_buffer_size = Some size }
169127let with_user user t = { t with user = Some user }
170128let with_output_format format t = { t with output_format = Some format }
171129172172-(* Helper codec for Model.t *)
173173-let model_jsont : Model.t Jsont.t =
174174- Jsont.map ~kind:"Model" ~dec:Model.of_string ~enc:Model.to_string Jsont.string
175175-176176-(* Helper codec for env - list of string pairs encoded as object.
177177- Env is a dynamic object where all values should be strings.
178178- Uses pattern matching to extract object members, then jsont for string decoding. *)
179179-let env_jsont : (string * string) list Jsont.t =
180180- Jsont.map ~kind:"Env"
181181- ~dec:(fun json ->
182182- match json with
183183- | Jsont.Object (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
190190- | _ -> [])
191191- ~enc:(fun 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))
197197- Jsont.json
198198-199199-let jsont : t Jsont.t =
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;
209209- permission_callback = Some Permissions.default_allow_callback;
210210- model;
211211- cwd = None;
212212- env;
213213- continue_conversation = false;
214214- resume = None;
215215- max_turns = None;
216216- permission_prompt_tool_name = None;
217217- settings = None;
218218- add_dirs = [];
219219- extra_args = [];
220220- debug_stderr = None;
221221- hooks = None;
222222- max_budget_usd = None;
223223- fallback_model = None;
224224- setting_sources = None;
225225- max_buffer_size = None;
226226- user = None;
227227- output_format = None;
228228- unknown;
229229- }
230230- in
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)
246246-247130let log_options t =
248248- Log.debug (fun m -> m "Claude options: %a" (Jsont.pp_value jsont ()) t)
131131+ Log.debug (fun m ->
132132+ m "Options: model=%s fallback=%s max_thinking_tokens=%d max_budget=%s"
133133+ (match t.model with
134134+ | None -> "default"
135135+ | Some m -> Proto.Model.to_string m)
136136+ (match t.fallback_model with
137137+ | None -> "none"
138138+ | Some m -> Proto.Model.to_string m)
139139+ t.max_thinking_tokens
140140+ (match t.max_budget_usd with
141141+ | None -> "unlimited"
142142+ | Some b -> Printf.sprintf "$%.2f" b))
143143+144144+module Advanced = struct
145145+ let to_wire (t : t) : Proto.Options.t =
146146+ let base = Proto.Options.empty in
147147+ let base = Proto.Options.with_allowed_tools t.allowed_tools base in
148148+ let base = Proto.Options.with_disallowed_tools t.disallowed_tools base in
149149+ let base = Proto.Options.with_max_thinking_tokens t.max_thinking_tokens base in
150150+ let base =
151151+ match t.system_prompt with
152152+ | None -> base
153153+ | Some p -> Proto.Options.with_system_prompt p base
154154+ in
155155+ let base =
156156+ match t.append_system_prompt with
157157+ | None -> base
158158+ | Some p -> Proto.Options.with_append_system_prompt p base
159159+ in
160160+ let base =
161161+ match t.permission_mode with
162162+ | None -> base
163163+ | Some m ->
164164+ Proto.Options.with_permission_mode (Permissions.Mode.to_proto m) base
165165+ in
166166+ let base =
167167+ match t.model with
168168+ | None -> base
169169+ | Some m -> Proto.Options.with_model m base
170170+ in
171171+ let base =
172172+ Proto.Options.with_continue_conversation t.continue_conversation base
173173+ in
174174+ let base =
175175+ match t.resume with
176176+ | None -> base
177177+ | Some r -> Proto.Options.with_resume r base
178178+ in
179179+ let base =
180180+ match t.max_turns with
181181+ | None -> base
182182+ | Some turns -> Proto.Options.with_max_turns turns base
183183+ in
184184+ let base =
185185+ match t.permission_prompt_tool_name with
186186+ | None -> base
187187+ | Some tool -> Proto.Options.with_permission_prompt_tool_name tool base
188188+ in
189189+ let base =
190190+ match t.settings with
191191+ | None -> base
192192+ | Some s -> Proto.Options.with_settings s base
193193+ in
194194+ let base = Proto.Options.with_add_dirs t.add_dirs base in
195195+ let base =
196196+ match t.max_budget_usd with
197197+ | None -> base
198198+ | Some b -> Proto.Options.with_max_budget_usd b base
199199+ in
200200+ let base =
201201+ match t.fallback_model with
202202+ | None -> base
203203+ | Some m -> Proto.Options.with_fallback_model m base
204204+ in
205205+ let base =
206206+ match t.setting_sources with
207207+ | None -> base
208208+ | Some sources -> Proto.Options.with_setting_sources sources base
209209+ in
210210+ let base =
211211+ match t.max_buffer_size with
212212+ | None -> base
213213+ | Some size -> Proto.Options.with_max_buffer_size size base
214214+ in
215215+ let base =
216216+ match t.user with
217217+ | None -> base
218218+ | Some u -> Proto.Options.with_user u base
219219+ in
220220+ let base =
221221+ match t.output_format with
222222+ | None -> base
223223+ | Some format -> Proto.Options.with_output_format format base
224224+ in
225225+ base
226226+end
+100-216
lib/options.mli
···2323 {[
2424 let options =
2525 Options.default
2626- |> Options.with_model "claude-sonnet-4-5"
2626+ |> Options.with_model `Sonnet_4_5
2727 |> Options.with_max_budget_usd 1.0
2828 |> Options.with_permission_mode Permissions.Mode.Accept_edits
2929 ]}
···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"
4040+ |> Options.with_model `Haiku_4
4141 ]}
42424343 {3 Production: Cost Control with Fallback}
···4545 {[
4646 let prod_config =
4747 Options.default
4848- |> Options.with_model "claude-sonnet-4-5"
4949- |> Options.with_fallback_model "claude-haiku-4"
4848+ |> Options.with_model `Sonnet_4_5
4949+ |> Options.with_fallback_model `Haiku_4
5050 |> Options.with_max_budget_usd 10.0 (* $10 daily limit *)
5151 |> Options.with_max_buffer_size 5_000_000
5252 ]}
···5656 {[
5757 let dev_config =
5858 Options.default
5959- |> Options.with_setting_sources [ User; Project ]
6059 |> Options.with_max_budget_usd 1.0
6160 |> Options.with_permission_mode Permissions.Mode.Default
6261 ]}
63626464- {3 Structured Output: Type-Safe Responses}
6565-6666- {[
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-8080- let format = Structured_output.of_json_schema schema
8181-8282- let analysis_config =
8383- Options.default
8484- |> Options.with_output_format format
8585- |> Options.with_allowed_tools [ "Read"; "Glob"; "Grep" ]
8686- ]}
8787-8863 {2 Advanced Options}
89649065 {3 Budget Control}
···94699570 {3 Settings Isolation}
96719797- Use {!with_setting_sources} or {!with_no_settings} to control which
9898- configuration files are loaded:
9999- - [User] - ~/.claude/config
100100- - [Project] - .claude/ in project root
101101- - [Local] - Current directory settings
102102- - [Some []] (via {!with_no_settings}) - No settings, fully isolated
103103-7272+ Use {!with_no_settings} to control which configuration files are loaded.
10473 This is critical for reproducible builds in CI/CD environments.
1057410675 {3 Model Fallback}
···11281(** The log source for options operations *)
1138211483(** {1 Types} *)
115115-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 *)
1248412585type t
12686(** The type of configuration options. *)
···13292 - Default allow permission callback
13393 - No custom prompts or model override *)
13494135135-val create :
136136- ?allowed_tools:string list ->
137137- ?disallowed_tools:string list ->
138138- ?max_thinking_tokens:int ->
139139- ?system_prompt:string ->
140140- ?append_system_prompt:string ->
141141- ?permission_mode:Permissions.Mode.t ->
142142- ?permission_callback:Permissions.callback ->
143143- ?model:Model.t ->
144144- ?cwd:Eio.Fs.dir_ty Eio.Path.t ->
145145- ?env:(string * string) list ->
146146- ?continue_conversation:bool ->
147147- ?resume:string ->
148148- ?max_turns:int ->
149149- ?permission_prompt_tool_name:string ->
150150- ?settings:string ->
151151- ?add_dirs:string list ->
152152- ?extra_args:(string * string option) list ->
153153- ?debug_stderr:Eio.Flow.sink_ty Eio.Flow.sink ->
154154- ?hooks:Hooks.config ->
155155- ?max_budget_usd:float ->
156156- ?fallback_model:Model.t ->
157157- ?setting_sources:setting_source list ->
158158- ?max_buffer_size:int ->
159159- ?user:string ->
160160- ?output_format:Structured_output.t ->
161161- ?unknown:Jsont.json ->
162162- unit ->
163163- t
164164-(** [create ?allowed_tools ?disallowed_tools ?max_thinking_tokens ?system_prompt
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.
170170- @param allowed_tools List of explicitly allowed tool names
171171- @param disallowed_tools List of explicitly disallowed tool names
172172- @param max_thinking_tokens
173173- Maximum tokens for thinking blocks (default: 8000)
174174- @param system_prompt Replace the default system prompt
175175- @param append_system_prompt Append to the default system prompt
176176- @param permission_mode Permission mode to use
177177- @param permission_callback Custom permission callback
178178- @param model Override the default model
179179- @param cwd Working directory for file operations
180180- @param env Environment variables to set
181181- @param continue_conversation Continue an existing conversation
182182- @param resume Resume from a specific session ID
183183- @param max_turns Maximum number of conversation turns
184184- @param permission_prompt_tool_name Tool name for permission prompts
185185- @param settings Path to settings file
186186- @param add_dirs Additional directories to allow access to
187187- @param extra_args Additional CLI flags to pass through
188188- @param debug_stderr Sink for debug output when debug-to-stderr is set
189189- @param hooks Hooks configuration for event interception
190190- @param max_budget_usd Hard spending limit in USD (terminates on exceed)
191191- @param fallback_model Automatic fallback on primary model unavailability
192192- @param setting_sources Control which settings load (user/project/local)
193193- @param max_buffer_size Control for stdout buffer size in bytes
194194- @param user Unix user for subprocess execution
195195- @param output_format Optional structured output format specification *)
196196-197197-(** {1 Accessors} *)
198198-199199-val allowed_tools : t -> string list
200200-(** [allowed_tools t] returns the list of allowed tools. *)
201201-202202-val disallowed_tools : t -> string list
203203-(** [disallowed_tools t] returns the list of disallowed tools. *)
204204-205205-val max_thinking_tokens : t -> int
206206-(** [max_thinking_tokens t] returns the maximum thinking tokens. *)
207207-208208-val system_prompt : t -> string option
209209-(** [system_prompt t] returns the optional system prompt override. *)
210210-211211-val append_system_prompt : t -> string option
212212-(** [append_system_prompt t] returns the optional system prompt append. *)
213213-214214-val permission_mode : t -> Permissions.Mode.t option
215215-(** [permission_mode t] returns the optional permission mode. *)
216216-217217-val permission_callback : t -> Permissions.callback option
218218-(** [permission_callback t] returns the optional permission callback. *)
219219-220220-val model : t -> Model.t option
221221-(** [model t] returns the optional model override. *)
222222-223223-val cwd : t -> Eio.Fs.dir_ty Eio.Path.t option
224224-(** [cwd t] returns the optional working directory. *)
225225-226226-val env : t -> (string * string) list
227227-(** [env t] returns the environment variables. *)
228228-229229-val continue_conversation : t -> bool
230230-(** [continue_conversation t] returns whether to continue an existing
231231- conversation. *)
232232-233233-val resume : t -> string option
234234-(** [resume t] returns the optional session ID to resume. *)
235235-236236-val max_turns : t -> int option
237237-(** [max_turns t] returns the optional maximum number of turns. *)
238238-239239-val permission_prompt_tool_name : t -> string option
240240-(** [permission_prompt_tool_name t] returns the optional tool name for
241241- permission prompts. *)
242242-243243-val settings : t -> string option
244244-(** [settings t] returns the optional path to settings file. *)
245245-246246-val add_dirs : t -> string list
247247-(** [add_dirs t] returns the list of additional allowed directories. *)
248248-249249-val extra_args : t -> (string * string option) list
250250-(** [extra_args t] returns the additional CLI flags. *)
251251-252252-val debug_stderr : t -> Eio.Flow.sink_ty Eio.Flow.sink option
253253-(** [debug_stderr t] returns the optional debug output sink. *)
254254-255255-val hooks : t -> Hooks.config option
256256-(** [hooks t] returns the optional hooks configuration. *)
257257-258258-val max_budget_usd : t -> float option
259259-(** [max_budget_usd t] returns the optional spending limit in USD. *)
260260-261261-val fallback_model : t -> Model.t option
262262-(** [fallback_model t] returns the optional fallback model. *)
263263-264264-val setting_sources : t -> setting_source list option
265265-(** [setting_sources t] returns the optional list of setting sources to load. *)
266266-267267-val max_buffer_size : t -> int option
268268-(** [max_buffer_size t] returns the optional stdout buffer size in bytes. *)
269269-270270-val user : t -> string option
271271-(** [user t] returns the optional Unix user for subprocess execution. *)
272272-273273-val output_format : t -> Structured_output.t option
274274-(** [output_format t] returns the optional structured output format. *)
275275-276276-val unknown : t -> Jsont.json
277277-(** [unknown t] returns any unknown JSON fields that were preserved during
278278- decoding. *)
279279-280280-(** {1 Builders} *)
9595+(** {1 Builder Pattern} *)
2819628297val with_allowed_tools : string list -> t -> t
28398(** [with_allowed_tools tools t] sets the allowed tools. *)
···300115val with_permission_callback : Permissions.callback -> t -> t
301116(** [with_permission_callback callback t] sets the permission callback. *)
302117303303-val with_model : Model.t -> t -> t
118118+val with_model : Proto.Model.t -> t -> t
304119(** [with_model model t] sets the model override using a typed Model.t. *)
305120306306-val with_model_string : string -> t -> t
307307-(** [with_model_string model t] sets the model override from a string. The
308308- string is parsed using {!Model.of_string}. *)
309309-310310-val with_cwd : Eio.Fs.dir_ty Eio.Path.t -> t -> t
121121+val with_cwd : [> Eio.Fs.dir_ty ] Eio.Path.t -> t -> t
311122(** [with_cwd cwd t] sets the working directory. *)
312123313124val with_env : (string * string) list -> t -> t
···333144val with_add_dirs : string list -> t -> t
334145(** [with_add_dirs dirs t] sets the additional allowed directories. *)
335146336336-val with_extra_args : (string * string option) list -> t -> t
337337-(** [with_extra_args args t] sets the additional CLI flags. *)
338338-339339-val with_debug_stderr : Eio.Flow.sink_ty Eio.Flow.sink -> t -> t
147147+val with_debug_stderr : [> Eio.Flow.sink_ty ] Eio.Flow.sink -> t -> t
340148(** [with_debug_stderr sink t] sets the debug output sink. *)
341149342342-val with_hooks : Hooks.config -> t -> t
150150+val with_hooks : Hooks.t -> t -> t
343151(** [with_hooks hooks t] sets the hooks configuration. *)
344152345153val with_max_budget_usd : float -> t -> t
346154(** [with_max_budget_usd budget t] sets the maximum spending limit in USD. The
347155 session will terminate if this limit is exceeded. *)
348156349349-val with_fallback_model : Model.t -> t -> t
157157+val with_fallback_model : Proto.Model.t -> t -> t
350158(** [with_fallback_model model t] sets the fallback model using a typed Model.t.
351159*)
352160353353-val with_fallback_model_string : string -> t -> t
354354-(** [with_fallback_model_string model t] sets the fallback model from a string.
355355- The string is parsed using {!Model.of_string}. *)
356356-357357-val with_setting_sources : setting_source list -> t -> t
358358-(** [with_setting_sources sources t] sets which configuration sources to load.
359359- Use empty list for isolated environments (e.g., CI/CD). *)
360360-361161val with_no_settings : t -> t
362162(** [with_no_settings t] disables all settings loading (user, project, local).
363163 Useful for CI/CD environments where you want isolated, reproducible
···370170val with_user : string -> t -> t
371171(** [with_user user t] sets the Unix user for subprocess execution. *)
372172373373-val with_output_format : Structured_output.t -> t -> t
173173+val with_output_format : Proto.Structured_output.t -> t -> t
374174(** [with_output_format format t] sets the structured output format. *)
375175376376-(** {1 Serialization} *)
176176+val with_extra_args : (string * string option) list -> t -> t
177177+(** [with_extra_args args t] sets the additional CLI flags. *)
178178+179179+(** {1 Accessors} *)
180180+181181+val allowed_tools : t -> string list
182182+(** [allowed_tools t] returns the list of allowed tools. *)
377183378378-val jsont : t Jsont.t
379379-(** [jsont] is the Jsont codec for Options.t Use [Jsont.pp_value jsont ()] for
380380- pretty-printing. *)
184184+val disallowed_tools : t -> string list
185185+(** [disallowed_tools t] returns the list of disallowed tools. *)
186186+187187+val max_thinking_tokens : t -> int
188188+(** [max_thinking_tokens t] returns the maximum thinking tokens. *)
189189+190190+val system_prompt : t -> string option
191191+(** [system_prompt t] returns the optional system prompt override. *)
192192+193193+val append_system_prompt : t -> string option
194194+(** [append_system_prompt t] returns the optional system prompt append. *)
195195+196196+val permission_mode : t -> Permissions.Mode.t option
197197+(** [permission_mode t] returns the optional permission mode. *)
198198+199199+val permission_callback : t -> Permissions.callback option
200200+(** [permission_callback t] returns the optional permission callback. *)
201201+202202+val model : t -> Proto.Model.t option
203203+(** [model t] returns the optional model override. *)
204204+205205+val cwd : t -> Eio.Fs.dir_ty Eio.Path.t option
206206+(** [cwd t] returns the optional working directory. *)
207207+208208+val env : t -> (string * string) list
209209+(** [env t] returns the environment variables. *)
210210+211211+val continue_conversation : t -> bool
212212+(** [continue_conversation t] returns whether to continue an existing
213213+ conversation. *)
214214+215215+val resume : t -> string option
216216+(** [resume t] returns the optional session ID to resume. *)
217217+218218+val max_turns : t -> int option
219219+(** [max_turns t] returns the optional maximum number of turns. *)
220220+221221+val permission_prompt_tool_name : t -> string option
222222+(** [permission_prompt_tool_name t] returns the optional tool name for
223223+ permission prompts. *)
224224+225225+val settings : t -> string option
226226+(** [settings t] returns the optional path to settings file. *)
227227+228228+val add_dirs : t -> string list
229229+(** [add_dirs t] returns the list of additional allowed directories. *)
230230+231231+val debug_stderr : t -> Eio.Flow.sink_ty Eio.Flow.sink option
232232+(** [debug_stderr t] returns the optional debug output sink. *)
233233+234234+val hooks : t -> Hooks.t option
235235+(** [hooks t] returns the optional hooks configuration. *)
236236+237237+val max_budget_usd : t -> float option
238238+(** [max_budget_usd t] returns the optional spending limit in USD. *)
239239+240240+val fallback_model : t -> Proto.Model.t option
241241+(** [fallback_model t] returns the optional fallback model. *)
242242+243243+val setting_sources : t -> Proto.Options.setting_source list option
244244+(** [setting_sources t] returns the optional list of setting sources to load. *)
245245+246246+val max_buffer_size : t -> int option
247247+(** [max_buffer_size t] returns the optional stdout buffer size in bytes. *)
248248+249249+val user : t -> string option
250250+(** [user t] returns the optional Unix user for subprocess execution. *)
251251+252252+val output_format : t -> Proto.Structured_output.t option
253253+(** [output_format t] returns the optional structured output format. *)
254254+255255+val extra_args : t -> (string * string option) list
256256+(** [extra_args t] returns the additional CLI flags. *)
381257382258(** {1 Logging} *)
383259384260val log_options : t -> unit
385261(** [log_options t] logs the current options configuration. *)
262262+263263+(** {1 Advanced: Wire Format Conversion} *)
264264+265265+module Advanced : sig
266266+ val to_wire : t -> Proto.Options.t
267267+ (** [to_wire t] converts to wire format (excludes Eio types and callbacks).
268268+ This is used internally by the client to send options to the Claude CLI. *)
269269+end
+68-234
lib/permissions.ml
···2121 raise
2222 (Invalid_argument (Printf.sprintf "Mode.of_string: unknown mode %s" s))
23232424- let jsont : t Jsont.t =
2525- Jsont.enum
2626- [
2727- ("default", Default);
2828- ("acceptEdits", Accept_edits);
2929- ("plan", Plan);
3030- ("bypassPermissions", Bypass_permissions);
3131- ]
3232-end
3333-3434-(** Permission behaviors *)
3535-module Behavior = struct
3636- type t = Allow | Deny | Ask
3737-3838- let to_string = function Allow -> "allow" | Deny -> "deny" | Ask -> "ask"
3939-4040- let of_string = function
4141- | "allow" -> Allow
4242- | "deny" -> Deny
4343- | "ask" -> Ask
4444- | s ->
4545- raise
4646- (Invalid_argument
4747- (Printf.sprintf "Behavior.of_string: unknown behavior %s" s))
2424+ let of_proto : Proto.Permissions.Mode.t -> t = function
2525+ | Proto.Permissions.Mode.Default -> Default
2626+ | Proto.Permissions.Mode.Accept_edits -> Accept_edits
2727+ | Proto.Permissions.Mode.Plan -> Plan
2828+ | Proto.Permissions.Mode.Bypass_permissions -> Bypass_permissions
48294949- let jsont : t Jsont.t =
5050- Jsont.enum [ ("allow", Allow); ("deny", Deny); ("ask", Ask) ]
3030+ let to_proto : t -> Proto.Permissions.Mode.t = function
3131+ | Default -> Proto.Permissions.Mode.Default
3232+ | Accept_edits -> Proto.Permissions.Mode.Accept_edits
3333+ | Plan -> Proto.Permissions.Mode.Plan
3434+ | Bypass_permissions -> Proto.Permissions.Mode.Bypass_permissions
5135end
52365337(** Permission rules *)
5438module Rule = struct
5555- type t = {
5656- tool_name : string;
5757- rule_content : string option;
5858- unknown : Unknown.t;
5959- }
6060-6161- let create ~tool_name ?rule_content ?(unknown = Unknown.empty) () =
6262- { tool_name; rule_content; unknown }
3939+ type t = { tool_name : string; rule_content : string option }
63404141+ let create ~tool_name ?rule_content () = { tool_name; rule_content }
6442 let tool_name t = t.tool_name
6543 let rule_content t = t.rule_content
6666- let unknown t = t.unknown
6767-6868- let jsont : t Jsont.t =
6969- let make tool_name rule_content unknown =
7070- { tool_name; rule_content; unknown }
7171- in
7272- Jsont.Object.map ~kind:"Rule" make
7373- |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
7474- |> Jsont.Object.opt_mem "rule_content" Jsont.string ~enc:rule_content
7575- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
7676- |> Jsont.Object.finish
7777-end
7878-7979-(** Permission updates *)
8080-module Update = struct
8181- type destination =
8282- | User_settings
8383- | Project_settings
8484- | Local_settings
8585- | Session
8686-8787- let _destination_of_string = function
8888- | "userSettings" -> User_settings
8989- | "projectSettings" -> Project_settings
9090- | "localSettings" -> Local_settings
9191- | "session" -> Session
9292- | s ->
9393- raise
9494- (Invalid_argument
9595- (Printf.sprintf "destination_of_string: unknown %s" s))
96449797- let destination_jsont : destination Jsont.t =
9898- Jsont.enum
9999- [
100100- ("userSettings", User_settings);
101101- ("projectSettings", Project_settings);
102102- ("localSettings", Local_settings);
103103- ("session", Session);
104104- ]
105105-106106- type update_type =
107107- | Add_rules
108108- | Replace_rules
109109- | Remove_rules
110110- | Set_mode
111111- | Add_directories
112112- | Remove_directories
113113-114114- let _update_type_of_string = function
115115- | "addRules" -> Add_rules
116116- | "replaceRules" -> Replace_rules
117117- | "removeRules" -> Remove_rules
118118- | "setMode" -> Set_mode
119119- | "addDirectories" -> Add_directories
120120- | "removeDirectories" -> Remove_directories
121121- | s ->
122122- raise
123123- (Invalid_argument
124124- (Printf.sprintf "update_type_of_string: unknown %s" s))
125125-126126- let update_type_jsont : update_type Jsont.t =
127127- Jsont.enum
128128- [
129129- ("addRules", Add_rules);
130130- ("replaceRules", Replace_rules);
131131- ("removeRules", Remove_rules);
132132- ("setMode", Set_mode);
133133- ("addDirectories", Add_directories);
134134- ("removeDirectories", Remove_directories);
135135- ]
136136-137137- type t = {
138138- update_type : update_type;
139139- rules : Rule.t list option;
140140- behavior : Behavior.t option;
141141- mode : Mode.t option;
142142- directories : string list option;
143143- destination : destination option;
144144- unknown : Unknown.t;
145145- }
146146-147147- let create ~update_type ?rules ?behavior ?mode ?directories ?destination
148148- ?(unknown = Unknown.empty) () =
149149- { update_type; rules; behavior; mode; directories; destination; unknown }
150150-151151- let update_type t = t.update_type
152152- let rules t = t.rules
153153- let behavior t = t.behavior
154154- let mode t = t.mode
155155- let directories t = t.directories
156156- let destination t = t.destination
157157- let unknown t = t.unknown
158158-159159- let jsont : t Jsont.t =
160160- let make update_type rules behavior mode directories destination unknown =
161161- { update_type; rules; behavior; mode; directories; destination; unknown }
162162- in
163163- Jsont.Object.map ~kind:"Update" make
164164- |> Jsont.Object.mem "type" update_type_jsont ~enc:update_type
165165- |> Jsont.Object.opt_mem "rules" (Jsont.list Rule.jsont) ~enc:rules
166166- |> Jsont.Object.opt_mem "behavior" Behavior.jsont ~enc:behavior
167167- |> Jsont.Object.opt_mem "mode" Mode.jsont ~enc:mode
168168- |> Jsont.Object.opt_mem "directories" (Jsont.list Jsont.string)
169169- ~enc:directories
170170- |> Jsont.Object.opt_mem "destination" destination_jsont ~enc:destination
171171- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
172172- |> Jsont.Object.finish
173173-end
4545+ let of_proto (proto : Proto.Permissions.Rule.t) : t =
4646+ {
4747+ tool_name = Proto.Permissions.Rule.tool_name proto;
4848+ rule_content = Proto.Permissions.Rule.rule_content proto;
4949+ }
17450175175-(** Permission context for callbacks *)
176176-module Context = struct
177177- type t = { suggestions : Update.t list; unknown : Unknown.t }
178178-179179- let create ?(suggestions = []) ?(unknown = Unknown.empty) () =
180180- { suggestions; unknown }
181181-182182- let suggestions t = t.suggestions
183183- let unknown t = t.unknown
184184-185185- let jsont : t Jsont.t =
186186- let make suggestions unknown = { suggestions; unknown } in
187187- Jsont.Object.map ~kind:"Context" make
188188- |> Jsont.Object.mem "suggestions" (Jsont.list Update.jsont) ~enc:suggestions
189189- ~dec_absent:[]
190190- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
191191- |> Jsont.Object.finish
5151+ let to_proto (t : t) : Proto.Permissions.Rule.t =
5252+ Proto.Permissions.Rule.create ~tool_name:t.tool_name ?rule_content:t.rule_content
5353+ ()
19254end
19355194194-(** Permission results *)
195195-module Result = struct
5656+(** Permission decisions *)
5757+module Decision = struct
19658 type t =
197197- | Allow of {
198198- updated_input : Jsont.json option;
199199- updated_permissions : Update.t list option;
200200- unknown : Unknown.t;
201201- }
202202- | Deny of { message : string; interrupt : bool; unknown : Unknown.t }
5959+ | Allow of { updated_input : Tool_input.t option }
6060+ | Deny of { message : string; interrupt : bool }
20361204204- let allow ?updated_input ?updated_permissions ?(unknown = Unknown.empty) () =
205205- Allow { updated_input; updated_permissions; unknown }
6262+ let allow ?updated_input () = Allow { updated_input }
6363+ let deny ~message ~interrupt = Deny { message; interrupt }
20664207207- let deny ~message ~interrupt ?(unknown = Unknown.empty) () =
208208- Deny { message; interrupt; unknown }
6565+ let is_allow = function Allow _ -> true | Deny _ -> false
6666+ let is_deny = function Allow _ -> false | Deny _ -> true
20967210210- let jsont : t Jsont.t =
211211- let allow_record =
212212- let make updated_input updated_permissions unknown =
213213- Allow { updated_input; updated_permissions; unknown }
214214- in
215215- Jsont.Object.map ~kind:"AllowRecord" make
216216- |> Jsont.Object.opt_mem "updated_input" Jsont.json ~enc:(function
217217- | Allow { updated_input; _ } -> updated_input
218218- | _ -> None)
219219- |> Jsont.Object.opt_mem "updated_permissions" (Jsont.list Update.jsont)
220220- ~enc:(function
221221- | Allow { updated_permissions; _ } -> updated_permissions
222222- | _ -> None)
223223- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(function
224224- | Allow { unknown; _ } -> unknown
225225- | _ -> Unknown.empty)
226226- |> Jsont.Object.finish
227227- in
228228- let deny_record =
229229- let make message interrupt unknown =
230230- Deny { message; interrupt; unknown }
231231- in
232232- Jsont.Object.map ~kind:"DenyRecord" make
233233- |> Jsont.Object.mem "message" Jsont.string ~enc:(function
234234- | Deny { message; _ } -> message
235235- | _ -> "")
236236- |> Jsont.Object.mem "interrupt" Jsont.bool ~enc:(function
237237- | Deny { interrupt; _ } -> interrupt
238238- | _ -> false)
239239- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(function
240240- | Deny { unknown; _ } -> unknown
241241- | _ -> Unknown.empty)
242242- |> Jsont.Object.finish
243243- in
244244- let case_allow =
245245- Jsont.Object.Case.map "allow" allow_record ~dec:(fun v -> v)
246246- in
247247- let case_deny =
248248- Jsont.Object.Case.map "deny" deny_record ~dec:(fun v -> v)
249249- in
6868+ let updated_input = function
6969+ | Allow { updated_input } -> updated_input
7070+ | Deny _ -> None
25071251251- let enc_case = function
252252- | Allow _ as v -> Jsont.Object.Case.value case_allow v
253253- | Deny _ as v -> Jsont.Object.Case.value case_deny v
254254- in
7272+ let deny_message = function
7373+ | Allow _ -> None
7474+ | Deny { message; _ } -> Some message
25575256256- let cases = Jsont.Object.Case.[ make case_allow; make case_deny ] in
7676+ let deny_interrupt = function Allow _ -> false | Deny { interrupt; _ } -> interrupt
25777258258- Jsont.Object.map ~kind:"Result" Fun.id
259259- |> Jsont.Object.case_mem "behavior" Jsont.string ~enc:Fun.id ~enc_case cases
260260- ~tag_to_string:Fun.id ~tag_compare:String.compare
261261- |> Jsont.Object.finish
7878+ let to_proto_result (t : t) : Proto.Permissions.Result.t =
7979+ match t with
8080+ | Allow { updated_input } ->
8181+ let updated_input_json =
8282+ Option.map Tool_input.to_json updated_input
8383+ in
8484+ Proto.Permissions.Result.allow ?updated_input:updated_input_json ()
8585+ | Deny { message; interrupt } ->
8686+ Proto.Permissions.Result.deny ~message ~interrupt ()
26287end
26388264264-type callback =
265265- tool_name:string -> input:Jsont.json -> context:Context.t -> Result.t
8989+(** Permission context *)
9090+type context = {
9191+ tool_name : string;
9292+ input : Tool_input.t;
9393+ suggested_rules : Rule.t list;
9494+}
9595+9696+let extract_rules_from_proto_updates updates =
9797+ List.concat_map
9898+ (fun update ->
9999+ match Proto.Permissions.Update.rules update with
100100+ | Some rules -> List.map Rule.of_proto rules
101101+ | None -> [])
102102+ updates
103103+266104(** Permission callback type *)
105105+type callback = context -> Decision.t
267106268107(** Default callbacks *)
269269-let default_allow_callback ~tool_name:_ ~input:_ ~context:_ = Result.allow ()
108108+let default_allow _ctx = Decision.allow ()
270109271271-let discovery_callback log ~tool_name:_ ~input:_ ~context =
272272- List.iter
273273- (fun update ->
274274- match Update.rules update with
275275- | Some rules -> List.iter (fun rule -> log := rule :: !log) rules
276276- | None -> ())
277277- (Context.suggestions context);
278278- Result.allow ()
110110+let discovery log ctx =
111111+ List.iter (fun rule -> log := rule :: !log) ctx.suggested_rules;
112112+ Decision.allow ()
279113280114(** Logging *)
281281-let log_permission_check ~tool_name ~result =
282282- match result with
283283- | Result.Allow _ ->
115115+let log_permission_check ~tool_name ~decision =
116116+ match decision with
117117+ | Decision.Allow _ ->
284118 Log.info (fun m -> m "Permission granted for tool: %s" tool_name)
285285- | Result.Deny { message; _ } ->
119119+ | Decision.Deny { message; _ } ->
286120 Log.warn (fun m ->
287121 m "Permission denied for tool %s: %s" tool_name message)
+63-176
lib/permissions.mli
···11-(** Permission system for Claude tool invocations.
11+(** Permission control for tool usage.
2233 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. *)
55+ modes, rules, decisions, and callbacks. *)
6677val src : Logs.Src.t
88-(** The log source for permission operations *)
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. *)
1615 type t =
1716 | Default (** Standard permission mode with normal checks *)
1817 | Accept_edits (** Automatically accept file edits *)
1918 | Plan (** Planning mode with restricted execution *)
2019 | 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. *)
···2626 (** [of_string s] parses a mode from 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. Use
3131- [Jsont.pp_value jsont ()] for pretty-printing. *)
3232-end
3333-3434-(** {1 Permission Behaviors} *)
3535-3636-module Behavior : sig
3737- (** Behaviors determine how permission requests are handled. *)
3838-3939- (** The type of permission behaviors. *)
4040- type t =
4141- | Allow (** Allow the operation *)
4242- | Deny (** Deny the operation *)
4343- | Ask (** Ask the user for permission *)
4444-4545- val to_string : t -> string
4646- (** [to_string t] converts a behavior to its string representation. *)
4747-4848- val of_string : string -> t
4949- (** [of_string s] parses a behavior from its string representation.
5050- @raise Invalid_argument if the string is not a valid behavior. *)
2929+ val of_proto : Proto.Permissions.Mode.t -> t
3030+ (** [of_proto proto] converts from the protocol representation. *)
51315252- val jsont : t Jsont.t
5353- (** [jsont] is the Jsont codec for permission behaviors. Use
5454- [Jsont.pp_value jsont ()] for pretty-printing. *)
3232+ val to_proto : t -> Proto.Permissions.Mode.t
3333+ (** [to_proto t] converts to the protocol representation. *)
5534end
56355736(** {1 Permission Rules} *)
···5938module Rule : sig
6039 (** Rules define specific permissions for tools. *)
61406262- type t = {
6363- tool_name : string; (** Name of the tool *)
6464- rule_content : string option; (** Optional rule specification *)
6565- unknown : Unknown.t; (** Unknown fields *)
6666- }
4141+ type t
6742 (** The type of permission rules. *)
68436969- val create :
7070- tool_name:string -> ?rule_content:string -> ?unknown:Unknown.t -> unit -> t
7171- (** [create ~tool_name ?rule_content ?unknown ()] creates a new rule.
4444+ val create : tool_name:string -> ?rule_content:string -> unit -> t
4545+ (** [create ~tool_name ?rule_content ()] creates a new rule.
7246 @param tool_name The name of the tool this rule applies to
7373- @param rule_content Optional rule specification or pattern
7474- @param unknown Optional unknown fields to preserve *)
4747+ @param rule_content Optional rule specification or pattern *)
75487649 val tool_name : t -> string
7750 (** [tool_name t] returns the tool name. *)
···7952 val rule_content : t -> string option
8053 (** [rule_content t] returns the optional rule content. *)
81548282- val unknown : t -> Unknown.t
8383- (** [unknown t] returns the unknown fields. *)
5555+ val of_proto : Proto.Permissions.Rule.t -> t
5656+ (** [of_proto proto] converts from the protocol representation. *)
84578585- val jsont : t Jsont.t
8686- (** [jsont] is the Jsont codec for permission rules. Use
8787- [Jsont.pp_value jsont ()] for pretty-printing. *)
5858+ val to_proto : t -> Proto.Permissions.Rule.t
5959+ (** [to_proto t] converts to the protocol representation. *)
8860end
89619090-(** {1 Permission Updates} *)
9191-9292-module Update : sig
9393- (** Updates modify permission settings. *)
6262+(** {1 Permission Decisions} *)
94639595- (** The destination for permission updates. *)
9696- type destination =
9797- | User_settings (** Apply to user settings *)
9898- | Project_settings (** Apply to project settings *)
9999- | Local_settings (** Apply to local settings *)
100100- | Session (** Apply to current session only *)
101101-102102- (** The type of permission update. *)
103103- type update_type =
104104- | Add_rules (** Add new rules *)
105105- | Replace_rules (** Replace existing rules *)
106106- | Remove_rules (** Remove rules *)
107107- | Set_mode (** Set permission mode *)
108108- | Add_directories (** Add allowed directories *)
109109- | Remove_directories (** Remove allowed directories *)
6464+module Decision : sig
6565+ (** Decisions represent the outcome of a permission check. *)
1106611167 type t
112112- (** The type of permission updates. *)
6868+ (** The type of permission decisions. *)
11369114114- val create :
115115- update_type:update_type ->
116116- ?rules:Rule.t list ->
117117- ?behavior:Behavior.t ->
118118- ?mode:Mode.t ->
119119- ?directories:string list ->
120120- ?destination:destination ->
121121- ?unknown:Unknown.t ->
122122- unit ->
123123- t
124124- (** [create ~update_type ?rules ?behavior ?mode ?directories ?destination
125125- ?unknown ()] creates a new permission update.
126126- @param update_type The type of update to perform
127127- @param rules Optional list of rules to add/remove/replace
128128- @param behavior Optional behavior to set
129129- @param mode Optional permission mode to set
130130- @param directories Optional directories to add/remove
131131- @param destination Optional destination for the update
132132- @param unknown Optional unknown fields to preserve *)
7070+ val allow : ?updated_input:Tool_input.t -> unit -> t
7171+ (** [allow ?updated_input ()] creates an allow decision.
7272+ @param updated_input Optional modified tool input *)
13373134134- val update_type : t -> update_type
135135- (** [update_type t] returns the update type. *)
7474+ val deny : message:string -> interrupt:bool -> t
7575+ (** [deny ~message ~interrupt] creates a deny decision.
7676+ @param message The reason for denying permission
7777+ @param interrupt Whether to interrupt further execution *)
13678137137- val rules : t -> Rule.t list option
138138- (** [rules t] returns the optional list of rules. *)
7979+ val is_allow : t -> bool
8080+ (** [is_allow t] returns true if the decision allows the operation. *)
13981140140- val behavior : t -> Behavior.t option
141141- (** [behavior t] returns the optional behavior. *)
8282+ val is_deny : t -> bool
8383+ (** [is_deny t] returns true if the decision denies the operation. *)
14284143143- val mode : t -> Mode.t option
144144- (** [mode t] returns the optional mode. *)
8585+ val updated_input : t -> Tool_input.t option
8686+ (** [updated_input t] returns the optional updated tool input if the decision
8787+ is allow. *)
14588146146- val directories : t -> string list option
147147- (** [directories t] returns the optional list of directories. *)
8989+ val deny_message : t -> string option
9090+ (** [deny_message t] returns the denial message if the decision is deny. *)
14891149149- val destination : t -> destination option
150150- (** [destination t] returns the optional destination. *)
151151-152152- val unknown : t -> Unknown.t
153153- (** [unknown t] returns the unknown fields. *)
9292+ val deny_interrupt : t -> bool
9393+ (** [deny_interrupt t] returns whether to interrupt if the decision is deny. *)
15494155155- val jsont : t Jsont.t
156156- (** [jsont] is the Jsont codec for permission updates. Use
157157- [Jsont.pp_value jsont ()] for pretty-printing. *)
9595+ val to_proto_result : t -> Proto.Permissions.Result.t
9696+ (** [to_proto_result t] converts to the protocol result representation. *)
15897end
1599816099(** {1 Permission Context} *)
161100162162-module Context : sig
163163- (** Context provided to permission callbacks. *)
164164-165165- type t = {
166166- suggestions : Update.t list; (** Suggested permission updates *)
167167- unknown : Unknown.t; (** Unknown fields *)
168168- }
169169- (** The type of permission context. *)
170170-171171- val create : ?suggestions:Update.t list -> ?unknown:Unknown.t -> unit -> t
172172- (** [create ?suggestions ?unknown ()] creates a new context.
173173- @param suggestions Optional list of suggested permission updates
174174- @param unknown Optional unknown fields to preserve *)
175175-176176- val suggestions : t -> Update.t list
177177- (** [suggestions t] returns the list of suggested updates. *)
178178-179179- val unknown : t -> Unknown.t
180180- (** [unknown t] returns the unknown fields. *)
181181-182182- val jsont : t Jsont.t
183183- (** [jsont] is the Jsont codec for permission context. Use
184184- [Jsont.pp_value jsont ()] for pretty-printing. *)
185185-end
101101+type context = {
102102+ tool_name : string; (** Name of the tool being invoked *)
103103+ input : Tool_input.t; (** Tool input parameters *)
104104+ suggested_rules : Rule.t list; (** Suggested permission rules *)
105105+}
106106+(** The context provided to permission callbacks. *)
186107187187-(** {1 Permission Results} *)
188188-189189-module Result : sig
190190- (** Results of permission checks. *)
191191-192192- type t =
193193- | Allow of {
194194- updated_input : Jsont.json option; (** Modified tool input *)
195195- updated_permissions : Update.t list option;
196196- (** Permission updates to apply *)
197197- unknown : Unknown.t; (** Unknown fields *)
198198- }
199199- | Deny of {
200200- message : string; (** Reason for denial *)
201201- interrupt : bool; (** Whether to interrupt execution *)
202202- unknown : Unknown.t; (** Unknown fields *)
203203- } (** The type of permission results. *)
204204-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.
213213- @param updated_input Optional modified tool input
214214- @param updated_permissions Optional permission updates to apply
215215- @param unknown Optional unknown fields to preserve *)
216216-217217- val deny : message:string -> interrupt:bool -> ?unknown:Unknown.t -> unit -> t
218218- (** [deny ~message ~interrupt ?unknown ()] creates a deny result.
219219- @param message The reason for denying permission
220220- @param interrupt Whether to interrupt further execution
221221- @param unknown Optional unknown fields to preserve *)
222222-223223- val jsont : t Jsont.t
224224- (** [jsont] is the Jsont codec for permission results. Use
225225- [Jsont.pp_value jsont ()] for pretty-printing. *)
226226-end
108108+val extract_rules_from_proto_updates : Proto.Permissions.Update.t list -> Rule.t list
109109+(** [extract_rules_from_proto_updates updates] extracts rules from protocol
110110+ permission updates. Used internally to convert protocol suggestions into
111111+ context rules. *)
227112228113(** {1 Permission Callbacks} *)
229114230230-type callback =
231231- tool_name:string -> input:Jsont.json -> context:Context.t -> Result.t
115115+type callback = context -> Decision.t
232116(** The type of permission callbacks. Callbacks are invoked when Claude attempts
233233- to use a tool, allowing custom permission logic. *)
117117+ to use a tool, allowing custom permission logic.
118118+119119+ The callback receives a typed context with the tool name, input, and
120120+ suggested rules, and returns a decision to allow or deny the operation. *)
234121235235-val default_allow_callback : callback
236236-(** [default_allow_callback] always allows tool invocations. *)
122122+val default_allow : callback
123123+(** [default_allow] always allows tool invocations. *)
237124238238-val discovery_callback : Rule.t list ref -> callback
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. *)
125125+val discovery : Rule.t list ref -> callback
126126+(** [discovery log] creates a callback that collects suggested rules into the
127127+ provided reference while allowing all operations. Useful for discovering
128128+ what permissions an operation requires. *)
242129243130(** {1 Logging} *)
244131245245-val log_permission_check : tool_name:string -> result:Result.t -> unit
246246-(** [log_permission_check ~tool_name ~result] logs a permission check result. *)
132132+val log_permission_check : tool_name:string -> decision:Decision.t -> unit
133133+(** [log_permission_check ~tool_name ~decision] logs a permission check result. *)
+115
lib/response.ml
···11+module Text = struct
22+ type t = Content_block.Text.t
33+44+ let content = Content_block.Text.text
55+ let of_block block = block
66+end
77+88+module Tool_use = struct
99+ type t = Content_block.Tool_use.t
1010+1111+ let id = Content_block.Tool_use.id
1212+ let name = Content_block.Tool_use.name
1313+ let input = Content_block.Tool_use.input
1414+ let of_block block = block
1515+end
1616+1717+module Thinking = struct
1818+ type t = Content_block.Thinking.t
1919+2020+ let content = Content_block.Thinking.thinking
2121+ let signature = Content_block.Thinking.signature
2222+ let of_block block = block
2323+end
2424+2525+module Init = struct
2626+ type t = Message.System.t
2727+2828+ let session_id = Message.System.session_id
2929+ let model = Message.System.model
3030+ let cwd = Message.System.cwd
3131+3232+ let of_system sys =
3333+ if Message.System.is_init sys then Some sys else None
3434+end
3535+3636+module Error = struct
3737+ type t =
3838+ | System_error of Message.System.t
3939+ | Assistant_error of Message.Assistant.t * Message.Assistant.error
4040+4141+ let message = function
4242+ | System_error sys ->
4343+ Option.value (Message.System.error_message sys) ~default:"Unknown error"
4444+ | Assistant_error (_, err) -> (
4545+ match err with
4646+ | `Authentication_failed -> "Authentication failed"
4747+ | `Billing_error -> "Billing error"
4848+ | `Rate_limit -> "Rate limit exceeded"
4949+ | `Invalid_request -> "Invalid request"
5050+ | `Server_error -> "Server error"
5151+ | `Unknown -> "Unknown error")
5252+5353+ let is_system_error = function System_error _ -> true | _ -> false
5454+5555+ let is_assistant_error = function Assistant_error _ -> true | _ -> false
5656+5757+ let of_system sys =
5858+ if Message.System.is_error sys then Some (System_error sys) else None
5959+6060+ let of_assistant msg =
6161+ match Message.Assistant.error msg with
6262+ | Some err -> Some (Assistant_error (msg, err))
6363+ | None -> None
6464+end
6565+6666+module Complete = struct
6767+ type t = Message.Result.t
6868+6969+ let duration_ms = Message.Result.duration_ms
7070+ let num_turns = Message.Result.num_turns
7171+ let session_id = Message.Result.session_id
7272+ let total_cost_usd = Message.Result.total_cost_usd
7373+ let usage = Message.Result.usage
7474+ let result_text = Message.Result.result_text
7575+ let structured_output = Message.Result.structured_output
7676+ let of_result result = result
7777+end
7878+7979+type t =
8080+ | Text of Text.t
8181+ | Tool_use of Tool_use.t
8282+ | Tool_result of Content_block.Tool_result.t
8383+ | Thinking of Thinking.t
8484+ | Init of Init.t
8585+ | Error of Error.t
8686+ | Complete of Complete.t
8787+8888+let of_message = function
8989+ | Message.User _ ->
9090+ (* User messages are inputs, not responses *)
9191+ []
9292+ | Message.Assistant msg -> (
9393+ (* Check for assistant error first *)
9494+ match Error.of_assistant msg with
9595+ | Some err -> [ Error err ]
9696+ | None ->
9797+ (* Convert content blocks to response events *)
9898+ Message.Assistant.content msg
9999+ |> List.map (function
100100+ | Content_block.Text text -> Text (Text.of_block text)
101101+ | Content_block.Tool_use tool -> Tool_use (Tool_use.of_block tool)
102102+ | Content_block.Tool_result result -> Tool_result result
103103+ | Content_block.Thinking thinking ->
104104+ Thinking (Thinking.of_block thinking)))
105105+ | Message.System sys -> (
106106+ (* System messages can be Init or Error *)
107107+ match Init.of_system sys with
108108+ | Some init -> [ Init init ]
109109+ | None -> (
110110+ match Error.of_system sys with
111111+ | Some err -> [ Error err ]
112112+ | None -> []))
113113+ | Message.Result result ->
114114+ (* Result messages become Complete events *)
115115+ [ Complete (Complete.of_result result) ]
+147
lib/response.mli
···11+(** High-level response events from Claude.
22+33+ This module provides a unified interface for handling different types of
44+ responses from Claude. It converts low-level message and content block types
55+ into high-level response events that are easier to work with in application
66+ code. *)
77+88+(** {1 Response Event Types} *)
99+1010+module Text : sig
1111+ (** Text content from the assistant. *)
1212+1313+ type t
1414+ (** The type of text response events (opaque). *)
1515+1616+ val content : t -> string
1717+ (** [content t] returns the text content. *)
1818+1919+ val of_block : Content_block.Text.t -> t
2020+ (** [of_block block] creates a text response from a content block. *)
2121+end
2222+2323+module Tool_use : sig
2424+ (** Tool invocation request from the assistant. *)
2525+2626+ type t
2727+ (** The type of tool use response events (opaque). *)
2828+2929+ val id : t -> string
3030+ (** [id t] returns the unique identifier of the tool use. *)
3131+3232+ val name : t -> string
3333+ (** [name t] returns the name of the tool being invoked. *)
3434+3535+ val input : t -> Tool_input.t
3636+ (** [input t] returns the input parameters for the tool. *)
3737+3838+ val of_block : Content_block.Tool_use.t -> t
3939+ (** [of_block block] creates a tool use response from a content block. *)
4040+end
4141+4242+module Thinking : sig
4343+ (** Internal reasoning from the assistant. *)
4444+4545+ type t
4646+ (** The type of thinking response events (opaque). *)
4747+4848+ val content : t -> string
4949+ (** [content t] returns the thinking content. *)
5050+5151+ val signature : t -> string
5252+ (** [signature t] returns the cryptographic signature. *)
5353+5454+ val of_block : Content_block.Thinking.t -> t
5555+ (** [of_block block] creates a thinking response from a content block. *)
5656+end
5757+5858+module Init : sig
5959+ (** Session initialization event. *)
6060+6161+ type t
6262+ (** The type of init response events (opaque). *)
6363+6464+ val session_id : t -> string option
6565+ (** [session_id t] returns the optional session identifier. *)
6666+6767+ val model : t -> string option
6868+ (** [model t] returns the optional model name. *)
6969+7070+ val cwd : t -> string option
7171+ (** [cwd t] returns the optional current working directory. *)
7272+7373+ val of_system : Message.System.t -> t option
7474+ (** [of_system sys] returns Some if system message is init, None if error. *)
7575+end
7676+7777+module Error : sig
7878+ (** Error events from system or assistant. *)
7979+8080+ type t
8181+ (** The type of error response events (opaque). *)
8282+8383+ val message : t -> string
8484+ (** [message t] returns the error message. *)
8585+8686+ val is_system_error : t -> bool
8787+ (** [is_system_error t] returns true if this is a system error. *)
8888+8989+ val is_assistant_error : t -> bool
9090+ (** [is_assistant_error t] returns true if this is an assistant error. *)
9191+9292+ val of_system : Message.System.t -> t option
9393+ (** [of_system sys] returns Some if system message is error, None if init. *)
9494+9595+ val of_assistant : Message.Assistant.t -> t option
9696+ (** [of_assistant msg] returns Some if assistant has error, None otherwise. *)
9797+end
9898+9999+module Complete : sig
100100+ (** Session completion event with final results. *)
101101+102102+ type t
103103+ (** The type of completion response events (opaque). *)
104104+105105+ val duration_ms : t -> int
106106+ (** [duration_ms t] returns the total duration in milliseconds. *)
107107+108108+ val num_turns : t -> int
109109+ (** [num_turns t] returns the number of conversation turns. *)
110110+111111+ val session_id : t -> string
112112+ (** [session_id t] returns the session identifier. *)
113113+114114+ val total_cost_usd : t -> float option
115115+ (** [total_cost_usd t] returns the optional total cost in USD. *)
116116+117117+ val usage : t -> Message.Result.Usage.t option
118118+ (** [usage t] returns the optional usage statistics. *)
119119+120120+ val result_text : t -> string option
121121+ (** [result_text t] returns the optional result string. *)
122122+123123+ val structured_output : t -> Jsont.json option
124124+ (** [structured_output t] returns the optional structured JSON output. *)
125125+126126+ val of_result : Message.Result.t -> t
127127+ (** [of_result result] creates a completion response from a result message. *)
128128+end
129129+130130+(** {1 Response Event Union Type} *)
131131+132132+type t =
133133+ | Text of Text.t (** Text content from assistant *)
134134+ | Tool_use of Tool_use.t (** Tool invocation request *)
135135+ | Tool_result of Content_block.Tool_result.t (** Tool result (pass-through) *)
136136+ | Thinking of Thinking.t (** Internal reasoning *)
137137+ | Init of Init.t (** Session initialization *)
138138+ | Error of Error.t (** Error event *)
139139+ | Complete of Complete.t (** Session completion *)
140140+ (** The type of response events that can be received from Claude. *)
141141+142142+(** {1 Conversion} *)
143143+144144+val of_message : Message.t -> t list
145145+(** [of_message msg] converts a message to response events. An assistant
146146+ message may produce multiple events (one per content block). User messages
147147+ produce empty lists since they are not responses. *)
···6666 subtype : [ `Can_use_tool ];
6767 tool_name : string;
6868 input : Jsont.json;
6969- permission_suggestions : Permissions.Update.t list option;
6969+ permission_suggestions : Proto.Permissions.Update.t list option;
7070 blocked_path : string option;
7171 unknown : Unknown.t;
7272 }
···81818282 type set_permission_mode = {
8383 subtype : [ `Set_permission_mode ];
8484- mode : Permissions.Mode.t;
8484+ mode : Proto.Permissions.Mode.t;
8585 unknown : Unknown.t;
8686 }
8787 (** Request to change permission mode. *)
···130130 val permission :
131131 tool_name:string ->
132132 input:Jsont.json ->
133133- ?permission_suggestions:Permissions.Update.t list ->
133133+ ?permission_suggestions:Proto.Permissions.Update.t list ->
134134 ?blocked_path:string ->
135135 ?unknown:Unknown.t ->
136136 unit ->
···143143 (** [initialize ?hooks ?unknown ()] creates an initialize request. *)
144144145145 val set_permission_mode :
146146- mode:Permissions.Mode.t -> ?unknown:Unknown.t -> unit -> t
146146+ mode:Proto.Permissions.Mode.t -> ?unknown:Unknown.t -> unit -> t
147147 (** [set_permission_mode ~mode ?unknown] creates a permission mode change
148148 request. *)
149149
lib/server_info.cmi
This is a binary file and will not be displayed.
+35
lib/server_info.ml
···11+(** Server capabilities and metadata. *)
22+33+type t = {
44+ version : string;
55+ capabilities : string list;
66+ commands : string list;
77+ output_styles : string list;
88+}
99+1010+let version t = t.version
1111+let capabilities t = t.capabilities
1212+let commands t = t.commands
1313+let output_styles t = t.output_styles
1414+1515+let has_capability t cap = List.mem cap t.capabilities
1616+1717+let supports_hooks t = has_capability t "hooks"
1818+1919+let supports_structured_output t = has_capability t "structured-output"
2020+2121+let of_proto (proto : Proto.Control.Server_info.t) : t =
2222+ {
2323+ version = Proto.Control.Server_info.version proto;
2424+ capabilities = Proto.Control.Server_info.capabilities proto;
2525+ commands = Proto.Control.Server_info.commands proto;
2626+ output_styles = Proto.Control.Server_info.output_styles proto;
2727+ }
2828+2929+let of_sdk_control (sdk : Sdk_control.Server_info.t) : t =
3030+ {
3131+ version = Sdk_control.Server_info.version sdk;
3232+ capabilities = Sdk_control.Server_info.capabilities sdk;
3333+ commands = Sdk_control.Server_info.commands sdk;
3434+ output_styles = Sdk_control.Server_info.output_styles sdk;
3535+ }
+43
lib/server_info.mli
···11+(** Server capabilities and metadata.
22+33+ This module provides a high-level interface for querying server capabilities
44+ and metadata. It wraps the underlying protocol representation and provides
55+ convenient accessors and capability checks. *)
66+77+(** {1 Server Information} *)
88+99+type t
1010+(** Server metadata and capabilities. *)
1111+1212+val version : t -> string
1313+(** [version t] returns the server version string. *)
1414+1515+val capabilities : t -> string list
1616+(** [capabilities t] returns the list of available server capabilities. *)
1717+1818+val commands : t -> string list
1919+(** [commands t] returns the list of available CLI commands. *)
2020+2121+val output_styles : t -> string list
2222+(** [output_styles t] returns the list of supported output formats. *)
2323+2424+(** {1 Capability Checks} *)
2525+2626+val has_capability : t -> string -> bool
2727+(** [has_capability t cap] returns true if the specified capability is
2828+ available. *)
2929+3030+val supports_hooks : t -> bool
3131+(** [supports_hooks t] checks if the hooks capability is available. *)
3232+3333+val supports_structured_output : t -> bool
3434+(** [supports_structured_output t] checks if the structured output capability
3535+ is available. *)
3636+3737+(** {1 Internal} *)
3838+3939+val of_proto : Proto.Control.Server_info.t -> t
4040+(** [of_proto proto] converts from the protocol representation. *)
4141+4242+val of_sdk_control : Sdk_control.Server_info.t -> t
4343+(** [of_sdk_control sdk] converts from the SDK control representation. *)
+144
lib/tool_input.ml
···11+(** Opaque tool input with typed accessors. *)
22+33+type t = Jsont.json
44+55+(** {1 Escape Hatch} *)
66+77+let to_json t = t
88+let of_json json = json
99+1010+(** {1 Helper Functions} *)
1111+1212+(* Extract members from JSON object, or return empty list if not an object *)
1313+let get_members = function
1414+ | Jsont.Object (members, _) -> members
1515+ | _ -> []
1616+1717+(* Find a member by key in the object *)
1818+let find_member key members =
1919+ List.find_map
2020+ (fun ((name, _), value) -> if name = key then Some value else None)
2121+ members
2222+2323+(** {1 Typed Accessors} *)
2424+2525+let get_string t key =
2626+ let members = get_members t in
2727+ match find_member key members with
2828+ | Some json -> (
2929+ match Jsont.Json.decode Jsont.string json with
3030+ | Ok s -> Some s
3131+ | Error _ -> None)
3232+ | None -> None
3333+3434+let get_int t key =
3535+ let members = get_members t in
3636+ match find_member key members with
3737+ | Some json -> (
3838+ match Jsont.Json.decode Jsont.int json with
3939+ | Ok i -> Some i
4040+ | Error _ -> None)
4141+ | None -> None
4242+4343+let get_bool t key =
4444+ let members = get_members t in
4545+ match find_member key members with
4646+ | Some json -> (
4747+ match Jsont.Json.decode Jsont.bool json with
4848+ | Ok b -> Some b
4949+ | Error _ -> None)
5050+ | None -> None
5151+5252+let get_float t key =
5353+ let members = get_members t in
5454+ match find_member key members with
5555+ | Some json -> (
5656+ match Jsont.Json.decode Jsont.number json with
5757+ | Ok f -> Some f
5858+ | Error _ -> None)
5959+ | None -> None
6060+6161+let get_string_list t key =
6262+ let members = get_members t in
6363+ match find_member key members with
6464+ | Some json -> (
6565+ match json with
6666+ | Jsont.Array (items, _) ->
6767+ let strings =
6868+ List.filter_map
6969+ (fun item ->
7070+ match Jsont.Json.decode Jsont.string item with
7171+ | Ok s -> Some s
7272+ | Error _ -> None)
7373+ items
7474+ in
7575+ (* Only return Some if all items were strings *)
7676+ if List.length strings = List.length items then Some strings else None
7777+ | _ -> None)
7878+ | None -> None
7979+8080+let keys t =
8181+ let members = get_members t in
8282+ List.map (fun ((name, _), _) -> name) members
8383+8484+let is_empty t =
8585+ match t with Jsont.Object ([], _) -> true | Jsont.Object _ -> false | _ -> true
8686+8787+(** {1 Construction} *)
8888+8989+let empty = Jsont.Object ([], Jsont.Meta.none)
9090+9191+let add_member key value t =
9292+ let members = get_members t in
9393+ let new_member = ((key, Jsont.Meta.none), value) in
9494+ (* Replace existing member or add new one *)
9595+ let filtered_members =
9696+ List.filter (fun ((name, _), _) -> name <> key) members
9797+ in
9898+ Jsont.Object (new_member :: filtered_members, Jsont.Meta.none)
9999+100100+let add_string key value t =
101101+ let json_value =
102102+ match Jsont.Json.encode Jsont.string value with
103103+ | Ok json -> json
104104+ | Error _ -> failwith "add_string: encoding failed"
105105+ in
106106+ add_member key json_value t
107107+108108+let add_int key value t =
109109+ let json_value =
110110+ match Jsont.Json.encode Jsont.int value with
111111+ | Ok json -> json
112112+ | Error _ -> failwith "add_int: encoding failed"
113113+ in
114114+ add_member key json_value t
115115+116116+let add_bool key value t =
117117+ let json_value =
118118+ match Jsont.Json.encode Jsont.bool value with
119119+ | Ok json -> json
120120+ | Error _ -> failwith "add_bool: encoding failed"
121121+ in
122122+ add_member key json_value t
123123+124124+let add_float key value t =
125125+ let json_value =
126126+ match Jsont.Json.encode Jsont.number value with
127127+ | Ok json -> json
128128+ | Error _ -> failwith "add_float: encoding failed"
129129+ in
130130+ add_member key json_value t
131131+132132+let of_assoc assoc =
133133+ let members =
134134+ List.map (fun (key, json) -> ((key, Jsont.Meta.none), json)) assoc
135135+ in
136136+ Jsont.Object (members, Jsont.Meta.none)
137137+138138+let of_string_pairs pairs =
139139+ let assoc =
140140+ List.map
141141+ (fun (key, value) -> (key, Jsont.String (value, Jsont.Meta.none)))
142142+ pairs
143143+ in
144144+ of_assoc assoc
+64
lib/tool_input.mli
···11+(** Opaque tool input with typed accessors.
22+33+ Tool inputs are JSON objects representing parameters passed to tools. This
44+ module provides type-safe accessors while hiding the JSON structure from
55+ most client code. *)
66+77+type t
88+(** Abstract type for tool inputs. *)
99+1010+(** {1 Typed Accessors} *)
1111+1212+val get_string : t -> string -> string option
1313+(** [get_string t key] returns the string value for [key], if present and a
1414+ string. *)
1515+1616+val get_int : t -> string -> int option
1717+(** [get_int t key] returns the integer value for [key], if present and an int. *)
1818+1919+val get_bool : t -> string -> bool option
2020+(** [get_bool t key] returns the boolean value for [key], if present and a bool. *)
2121+2222+val get_float : t -> string -> float option
2323+(** [get_float t key] returns the float value for [key], if present and a float. *)
2424+2525+val get_string_list : t -> string -> string list option
2626+(** [get_string_list t key] returns the string list for [key], if present and a
2727+ list of strings. *)
2828+2929+val keys : t -> string list
3030+(** [keys t] returns all keys in the input. *)
3131+3232+val is_empty : t -> bool
3333+(** [is_empty t] returns true if the input has no keys. *)
3434+3535+(** {1 Escape Hatch} *)
3636+3737+val to_json : t -> Jsont.json
3838+(** [to_json t] returns the underlying JSON for advanced use cases. *)
3939+4040+val of_json : Jsont.json -> t
4141+(** [of_json json] wraps JSON as a tool input. *)
4242+4343+(** {1 Construction} *)
4444+4545+val empty : t
4646+(** [empty] is an empty tool input. *)
4747+4848+val add_string : string -> string -> t -> t
4949+(** [add_string key value t] adds a string field. *)
5050+5151+val add_int : string -> int -> t -> t
5252+(** [add_int key value t] adds an integer field. *)
5353+5454+val add_bool : string -> bool -> t -> t
5555+(** [add_bool key value t] adds a boolean field. *)
5656+5757+val add_float : string -> float -> t -> t
5858+(** [add_float key value t] adds a float field. *)
5959+6060+val of_assoc : (string * Jsont.json) list -> t
6161+(** [of_assoc assoc] creates tool input from an association list. *)
6262+6363+val of_string_pairs : (string * string) list -> t
6464+(** [of_string_pairs pairs] creates tool input from string key-value pairs. *)
+11-36
lib/transport.ml
···1818}
19192020let setting_source_to_string = function
2121- | Options.User -> "user"
2222- | Options.Project -> "project"
2323- | Options.Local -> "local"
2121+ | Proto.Options.User -> "user"
2222+ | Proto.Options.Project -> "project"
2323+ | Proto.Options.Local -> "local"
24242525let build_command ~claude_path ~options =
2626 let cmd = [ claude_path; "--output-format"; "stream-json"; "--verbose" ] in
···9696 let cmd =
9797 match Options.output_format options with
9898 | Some format ->
9999- let schema = Structured_output.json_schema format in
9999+ let schema = Proto.Structured_output.to_json_schema format in
100100 let schema_str =
101101 match Jsont_bytesrw.encode_string' Jsont.json schema with
102102 | Ok s -> s
···230230 (Printf.sprintf "Failed to receive message: %s"
231231 (Printexc.to_string exn)))
232232233233-(** Wire codec for interrupt response messages. *)
234234-module Interrupt_wire = struct
235235- type inner = { subtype : string; request_id : string }
236236- type t = { type_ : string; response : inner }
237237-238238- let inner_jsont : inner Jsont.t =
239239- let make subtype request_id = { subtype; request_id } in
240240- Jsont.Object.map ~kind:"InterruptInner" make
241241- |> Jsont.Object.mem "subtype" Jsont.string ~enc:(fun r -> r.subtype)
242242- |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun r -> r.request_id)
243243- |> Jsont.Object.finish
244244-245245- let jsont : t Jsont.t =
246246- let make type_ response = { type_; response } in
247247- Jsont.Object.map ~kind:"InterruptOuter" make
248248- |> Jsont.Object.mem "type" Jsont.string ~enc:(fun r -> r.type_)
249249- |> Jsont.Object.mem "response" inner_jsont ~enc:(fun r -> r.response)
250250- |> Jsont.Object.finish
251251-252252- let encode () =
253253- let wire =
254254- {
255255- type_ = "control_response";
256256- response = { subtype = "interrupt"; request_id = "" };
257257- }
258258- in
259259- match Jsont.Json.encode jsont wire with
260260- | Ok json -> json
261261- | Error msg -> failwith ("Interrupt_wire.encode: " ^ msg)
262262-end
263263-264233let interrupt t =
265234 Log.info (fun m -> m "Sending interrupt signal");
266266- let interrupt_msg = Interrupt_wire.encode () in
235235+ (* Create interrupt request using Proto types *)
236236+ let request = Proto.Control.Request.interrupt () in
237237+ let envelope =
238238+ Proto.Control.create_request ~request_id:"" ~request ()
239239+ in
240240+ let outgoing = Proto.Outgoing.Control_request envelope in
241241+ let interrupt_msg = Proto.Outgoing.to_json outgoing in
267242 send t interrupt_msg
268243269244let close t =
+131
proto/content_block.ml
···11+module Text = struct
22+ type t = { text : string; unknown : Unknown.t }
33+44+ let create text = { text; unknown = Unknown.empty }
55+ let make text unknown = { text; unknown }
66+ let text t = t.text
77+ let unknown t = t.unknown
88+99+ let jsont : t Jsont.t =
1010+ Jsont.Object.map ~kind:"Text" make
1111+ |> Jsont.Object.mem "text" Jsont.string ~enc:text
1212+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
1313+ |> Jsont.Object.finish
1414+end
1515+1616+module Tool_use = struct
1717+ type t = { id : string; name : string; input : Jsont.json; unknown : Unknown.t }
1818+1919+ let create ~id ~name ~input = { id; name; input; unknown = Unknown.empty }
2020+ let make id name input unknown = { id; name; input; unknown }
2121+ let id t = t.id
2222+ let name t = t.name
2323+ let input t = t.input
2424+ let unknown t = t.unknown
2525+2626+ let jsont : t Jsont.t =
2727+ Jsont.Object.map ~kind:"Tool_use" make
2828+ |> Jsont.Object.mem "id" Jsont.string ~enc:id
2929+ |> Jsont.Object.mem "name" Jsont.string ~enc:name
3030+ |> Jsont.Object.mem "input" Jsont.json ~enc:input
3131+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
3232+ |> Jsont.Object.finish
3333+end
3434+3535+module Tool_result = struct
3636+ type t = {
3737+ tool_use_id : string;
3838+ content : string option;
3939+ is_error : bool option;
4040+ unknown : Unknown.t;
4141+ }
4242+4343+ let create ~tool_use_id ?content ?is_error () =
4444+ { tool_use_id; content; is_error; unknown = Unknown.empty }
4545+4646+ let make tool_use_id content is_error unknown =
4747+ { tool_use_id; content; is_error; unknown }
4848+4949+ let tool_use_id t = t.tool_use_id
5050+ let content t = t.content
5151+ let is_error t = t.is_error
5252+ let unknown t = t.unknown
5353+5454+ let jsont : t Jsont.t =
5555+ Jsont.Object.map ~kind:"Tool_result" make
5656+ |> Jsont.Object.mem "tool_use_id" Jsont.string ~enc:tool_use_id
5757+ |> Jsont.Object.opt_mem "content" Jsont.string ~enc:content
5858+ |> Jsont.Object.opt_mem "is_error" Jsont.bool ~enc:is_error
5959+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
6060+ |> Jsont.Object.finish
6161+end
6262+6363+module Thinking = struct
6464+ type t = { thinking : string; signature : string; unknown : Unknown.t }
6565+6666+ let create ~thinking ~signature =
6767+ { thinking; signature; unknown = Unknown.empty }
6868+6969+ let make thinking signature unknown = { thinking; signature; unknown }
7070+ let thinking t = t.thinking
7171+ let signature t = t.signature
7272+ let unknown t = t.unknown
7373+7474+ let jsont : t Jsont.t =
7575+ Jsont.Object.map ~kind:"Thinking" make
7676+ |> Jsont.Object.mem "thinking" Jsont.string ~enc:thinking
7777+ |> Jsont.Object.mem "signature" Jsont.string ~enc:signature
7878+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
7979+ |> Jsont.Object.finish
8080+end
8181+8282+type t =
8383+ | Text of Text.t
8484+ | Tool_use of Tool_use.t
8585+ | Tool_result of Tool_result.t
8686+ | Thinking of Thinking.t
8787+8888+let text s = Text (Text.create s)
8989+let tool_use ~id ~name ~input = Tool_use (Tool_use.create ~id ~name ~input)
9090+9191+let tool_result ~tool_use_id ?content ?is_error () =
9292+ Tool_result (Tool_result.create ~tool_use_id ?content ?is_error ())
9393+9494+let thinking ~thinking ~signature =
9595+ Thinking (Thinking.create ~thinking ~signature)
9696+9797+let jsont : t Jsont.t =
9898+ let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
9999+100100+ let case_text = case_map "text" Text.jsont (fun v -> Text v) in
101101+ let case_tool_use =
102102+ case_map "tool_use" Tool_use.jsont (fun v -> Tool_use v)
103103+ in
104104+ let case_tool_result =
105105+ case_map "tool_result" Tool_result.jsont (fun v -> Tool_result v)
106106+ in
107107+ let case_thinking =
108108+ case_map "thinking" Thinking.jsont (fun v -> Thinking v)
109109+ in
110110+111111+ let enc_case = function
112112+ | Text v -> Jsont.Object.Case.value case_text v
113113+ | Tool_use v -> Jsont.Object.Case.value case_tool_use v
114114+ | Tool_result v -> Jsont.Object.Case.value case_tool_result v
115115+ | Thinking v -> Jsont.Object.Case.value case_thinking v
116116+ in
117117+118118+ let cases =
119119+ Jsont.Object.Case.
120120+ [
121121+ make case_text;
122122+ make case_tool_use;
123123+ make case_tool_result;
124124+ make case_thinking;
125125+ ]
126126+ in
127127+128128+ Jsont.Object.map ~kind:"Content_block" Fun.id
129129+ |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
130130+ ~tag_to_string:Fun.id ~tag_compare:String.compare
131131+ |> Jsont.Object.finish
+131
proto/content_block.ml.bak
···11+module Text = struct
22+ type t = { text : string; unknown : Unknown.t }
33+44+ let create text = { text; unknown = Unknown.empty }
55+ let make text unknown = { text; unknown }
66+ let text t = t.text
77+ let unknown t = t.unknown
88+99+ let jsont : t Jsont.t =
1010+ Jsont.Object.map ~kind:"Text" make
1111+ |> Jsont.Object.mem "text" Jsont.string ~enc:text
1212+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
1313+ |> Jsont.Object.finish
1414+end
1515+1616+module Tool_use = struct
1717+ type t = { id : string; name : string; input : Jsont.json; unknown : Unknown.t }
1818+1919+ let create ~id ~name ~input = { id; name; input; unknown = Unknown.empty }
2020+ let make id name input unknown = { id; name; input; unknown }
2121+ let id t = t.id
2222+ let name t = t.name
2323+ let input t = t.input
2424+ let unknown t = t.unknown
2525+2626+ let jsont : t Jsont.t =
2727+ Jsont.Object.map ~kind:"Tool_use" make
2828+ |> Jsont.Object.mem "id" Jsont.string ~enc:id
2929+ |> Jsont.Object.mem "name" Jsont.string ~enc:name
3030+ |> Jsont.Object.mem "input" Jsont.json ~enc:input
3131+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
3232+ |> Jsont.Object.finish
3333+end
3434+3535+module Tool_result = struct
3636+ type t = {
3737+ tool_use_id : string;
3838+ content : string option;
3939+ is_error : bool option;
4040+ unknown : Unknown.t;
4141+ }
4242+4343+ let create ~tool_use_id ?content ?is_error () =
4444+ { tool_use_id; content; is_error; unknown = Unknown.empty }
4545+4646+ let make tool_use_id content is_error unknown =
4747+ { tool_use_id; content; is_error; unknown }
4848+4949+ let tool_use_id t = t.tool_use_id
5050+ let content t = t.content
5151+ let is_error t = t.is_error
5252+ let unknown t = t.unknown
5353+5454+ let jsont : t Jsont.t =
5555+ Jsont.Object.map ~kind:"Tool_result" make
5656+ |> Jsont.Object.mem "tool_use_id" Jsont.string ~enc:tool_use_id
5757+ |> Jsont.Object.opt_mem "content" Jsont.string ~enc:content
5858+ |> Jsont.Object.opt_mem "is_error" Jsont.bool ~enc:is_error
5959+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
6060+ |> Jsont.Object.finish
6161+end
6262+6363+module Thinking = struct
6464+ type t = { thinking : string; signature : string; unknown : Unknown.t }
6565+6666+ let create ~thinking ~signature =
6767+ { thinking; signature; unknown = Unknown.empty }
6868+6969+ let make thinking signature unknown = { thinking; signature; unknown }
7070+ let thinking t = t.thinking
7171+ let signature t = t.signature
7272+ let unknown t = t.unknown
7373+7474+ let jsont : t Jsont.t =
7575+ Jsont.Object.map ~kind:"Thinking" make
7676+ |> Jsont.Object.mem "thinking" Jsont.string ~enc:thinking
7777+ |> Jsont.Object.mem "signature" Jsont.string ~enc:signature
7878+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
7979+ |> Jsont.Object.finish
8080+end
8181+8282+type t =
8383+ | Text of Text.t
8484+ | Tool_use of Tool_use.t
8585+ | Tool_result of Tool_result.t
8686+ | Thinking of Thinking.t
8787+8888+let text s = Text (Text.create s)
8989+let tool_use ~id ~name ~input = Tool_use (Tool_use.create ~id ~name ~input)
9090+9191+let tool_result ~tool_use_id ?content ?is_error () =
9292+ Tool_result (Tool_result.create ~tool_use_id ?content ?is_error ())
9393+9494+let thinking ~thinking ~signature =
9595+ Thinking (Thinking.create ~thinking ~signature)
9696+9797+let jsont : t Jsont.t =
9898+ let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
9999+100100+ let case_text = case_map "text" Text.jsont (fun v -> Text v) in
101101+ let case_tool_use =
102102+ case_map "tool_use" Tool_use.jsont (fun v -> Tool_use v)
103103+ in
104104+ let case_tool_result =
105105+ case_map "tool_result" Tool_result.jsont (fun v -> Tool_result v)
106106+ in
107107+ let case_thinking =
108108+ case_map "thinking" Thinking.jsont (fun v -> Thinking v)
109109+ in
110110+111111+ let enc_case = function
112112+ | Text v -> Jsont.Object.Case.value case_text v
113113+ | Tool_use v -> Jsont.Object.Case.value case_tool_use v
114114+ | Tool_result v -> Jsont.Object.Case.value case_tool_result v
115115+ | Thinking v -> Jsont.Object.Case.value case_thinking v
116116+ in
117117+118118+ let cases =
119119+ Jsont.Object.Case.
120120+ [
121121+ make case_text;
122122+ make case_tool_use;
123123+ make case_tool_result;
124124+ make case_thinking;
125125+ ]
126126+ in
127127+128128+ Jsont.Object.map ~kind:"Content_block" Fun.id
129129+ |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
130130+ ~tag_to_string:Fun.id ~tag_compare:String.compare
131131+ |> Jsont.Object.finish
+151
proto/content_block.mli
···11+(** Content blocks for Claude messages wire format.
22+33+ This module defines the wire format types for content blocks that can appear
44+ in Claude messages, including text, tool use, tool results, and thinking
55+ blocks. *)
66+77+(** {1 Text Blocks} *)
88+99+module Text : sig
1010+ (** Plain text content blocks. *)
1111+1212+ type t
1313+ (** The type of text blocks. *)
1414+1515+ val jsont : t Jsont.t
1616+ (** [jsont] is the Jsont codec for text blocks. Use [Jsont.Json.encode jsont]
1717+ and [Jsont.Json.decode jsont] for serialization. Use
1818+ [Jsont.pp_value jsont ()] for pretty-printing. *)
1919+2020+ val create : string -> t
2121+ (** [create text] creates a new text block with the given text content. *)
2222+2323+ val text : t -> string
2424+ (** [text t] returns the text content of the block. *)
2525+2626+ val unknown : t -> Unknown.t
2727+ (** [unknown t] returns any unknown fields from JSON parsing. *)
2828+end
2929+3030+(** {1 Tool Use Blocks} *)
3131+3232+module Tool_use : sig
3333+ (** Tool invocation requests from the assistant. *)
3434+3535+ type t
3636+ (** The type of tool use blocks. *)
3737+3838+ val jsont : t Jsont.t
3939+ (** [jsont] is the Jsont codec for tool use blocks. Use
4040+ [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
4141+ Use [Jsont.pp_value jsont ()] for pretty-printing. *)
4242+4343+ val create : id:string -> name:string -> input:Jsont.json -> t
4444+ (** [create ~id ~name ~input] creates a new tool use block.
4545+ @param id Unique identifier for this tool invocation
4646+ @param name Name of the tool to invoke
4747+ @param input Parameters for the tool as raw JSON *)
4848+4949+ val id : t -> string
5050+ (** [id t] returns the unique identifier of the tool use. *)
5151+5252+ val name : t -> string
5353+ (** [name t] returns the name of the tool being invoked. *)
5454+5555+ val input : t -> Jsont.json
5656+ (** [input t] returns the input parameters for the tool as raw JSON. *)
5757+5858+ val unknown : t -> Unknown.t
5959+ (** [unknown t] returns any unknown fields from JSON parsing. *)
6060+end
6161+6262+(** {1 Tool Result Blocks} *)
6363+6464+module Tool_result : sig
6565+ (** Results from tool invocations. *)
6666+6767+ type t
6868+ (** The type of tool result blocks. *)
6969+7070+ val jsont : t Jsont.t
7171+ (** [jsont] is the Jsont codec for tool result blocks. Use
7272+ [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
7373+ Use [Jsont.pp_value jsont ()] for pretty-printing. *)
7474+7575+ val create :
7676+ tool_use_id:string -> ?content:string -> ?is_error:bool -> unit -> t
7777+ (** [create ~tool_use_id ?content ?is_error ()] creates a new tool result
7878+ block.
7979+ @param tool_use_id The ID of the corresponding tool use block
8080+ @param content Optional result content
8181+ @param is_error Whether the tool execution resulted in an error *)
8282+8383+ val tool_use_id : t -> string
8484+ (** [tool_use_id t] returns the ID of the corresponding tool use. *)
8585+8686+ val content : t -> string option
8787+ (** [content t] returns the optional result content. *)
8888+8989+ val is_error : t -> bool option
9090+ (** [is_error t] returns whether this result represents an error. *)
9191+9292+ val unknown : t -> Unknown.t
9393+ (** [unknown t] returns any unknown fields from JSON parsing. *)
9494+end
9595+9696+(** {1 Thinking Blocks} *)
9797+9898+module Thinking : sig
9999+ (** Assistant's internal reasoning blocks. *)
100100+101101+ type t
102102+ (** The type of thinking blocks. *)
103103+104104+ val jsont : t Jsont.t
105105+ (** [jsont] is the Jsont codec for thinking blocks. Use
106106+ [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
107107+ Use [Jsont.pp_value jsont ()] for pretty-printing. *)
108108+109109+ val create : thinking:string -> signature:string -> t
110110+ (** [create ~thinking ~signature] creates a new thinking block.
111111+ @param thinking The assistant's internal reasoning
112112+ @param signature Cryptographic signature for verification *)
113113+114114+ val thinking : t -> string
115115+ (** [thinking t] returns the thinking content. *)
116116+117117+ val signature : t -> string
118118+ (** [signature t] returns the cryptographic signature. *)
119119+120120+ val unknown : t -> Unknown.t
121121+ (** [unknown t] returns any unknown fields from JSON parsing. *)
122122+end
123123+124124+(** {1 Content Block Union Type} *)
125125+126126+type t =
127127+ | Text of Text.t
128128+ | Tool_use of Tool_use.t
129129+ | Tool_result of Tool_result.t
130130+ | Thinking of Thinking.t
131131+ (** The type of content blocks, which can be text, tool use, tool result,
132132+ or thinking. *)
133133+134134+val jsont : t Jsont.t
135135+(** [jsont] is the Jsont codec for content blocks. Use [Jsont.Json.encode jsont]
136136+ and [Jsont.Json.decode jsont] for serialization. Use
137137+ [Jsont.pp_value jsont ()] for pretty-printing. *)
138138+139139+val text : string -> t
140140+(** [text s] creates a text content block. *)
141141+142142+val tool_use : id:string -> name:string -> input:Jsont.json -> t
143143+(** [tool_use ~id ~name ~input] creates a tool use content block. *)
144144+145145+val tool_result :
146146+ tool_use_id:string -> ?content:string -> ?is_error:bool -> unit -> t
147147+(** [tool_result ~tool_use_id ?content ?is_error ()] creates a tool result
148148+ content block. *)
149149+150150+val thinking : thinking:string -> signature:string -> t
151151+(** [thinking ~thinking ~signature] creates a thinking content block. *)
+346
proto/control.ml
···11+(** Control protocol wire format for SDK communication. *)
22+33+module Request = struct
44+ (* Individual record types for each request variant - private to this module *)
55+ type permission_r = {
66+ tool_name : string;
77+ input : Jsont.json;
88+ permission_suggestions : Permissions.Update.t list option;
99+ blocked_path : string option;
1010+ unknown : Unknown.t;
1111+ }
1212+1313+ type initialize_r = {
1414+ hooks : (string * Jsont.json) list option;
1515+ unknown : Unknown.t;
1616+ }
1717+1818+ type set_permission_mode_r = {
1919+ mode : Permissions.Mode.t;
2020+ unknown : Unknown.t;
2121+ }
2222+2323+ type hook_callback_r = {
2424+ callback_id : string;
2525+ input : Jsont.json;
2626+ tool_use_id : string option;
2727+ unknown : Unknown.t;
2828+ }
2929+3030+ type mcp_message_r = {
3131+ server_name : string;
3232+ message : Jsont.json;
3333+ unknown : Unknown.t;
3434+ }
3535+3636+ type set_model_r = { model : string; unknown : Unknown.t }
3737+3838+ type t =
3939+ | Interrupt
4040+ | Permission of permission_r
4141+ | Initialize of initialize_r
4242+ | Set_permission_mode of set_permission_mode_r
4343+ | Hook_callback of hook_callback_r
4444+ | Mcp_message of mcp_message_r
4545+ | Set_model of set_model_r
4646+ | Get_server_info
4747+4848+ let interrupt () = Interrupt
4949+5050+ let permission ~tool_name ~input ?permission_suggestions ?blocked_path () =
5151+ Permission
5252+ {
5353+ tool_name;
5454+ input;
5555+ permission_suggestions;
5656+ blocked_path;
5757+ unknown = Unknown.empty;
5858+ }
5959+6060+ let initialize ?hooks () = Initialize { hooks; unknown = Unknown.empty }
6161+6262+ let set_permission_mode ~mode () =
6363+ Set_permission_mode { mode; unknown = Unknown.empty }
6464+6565+ let hook_callback ~callback_id ~input ?tool_use_id () =
6666+ Hook_callback
6767+ { callback_id; input; tool_use_id; unknown = Unknown.empty }
6868+6969+ let mcp_message ~server_name ~message () =
7070+ Mcp_message { server_name; message; unknown = Unknown.empty }
7171+7272+ let set_model ~model () = Set_model { model; unknown = Unknown.empty }
7373+ let get_server_info () = Get_server_info
7474+7575+ (* Individual record codecs *)
7676+ let interrupt_jsont : unit Jsont.t =
7777+ Jsont.Object.map ~kind:"Interrupt" (fun _unknown -> ())
7878+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun () -> Unknown.empty)
7979+ |> Jsont.Object.finish
8080+8181+ let permission_jsont : permission_r Jsont.t =
8282+ let make tool_name input permission_suggestions blocked_path unknown :
8383+ permission_r =
8484+ { tool_name; input; permission_suggestions; blocked_path; unknown }
8585+ in
8686+ (Jsont.Object.map ~kind:"Permission" make
8787+ |> Jsont.Object.mem "toolName" Jsont.string
8888+ ~enc:(fun (r : permission_r) -> r.tool_name)
8989+ |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : permission_r) -> r.input)
9090+ |> Jsont.Object.opt_mem "permissionSuggestions"
9191+ (Jsont.list Permissions.Update.jsont) ~enc:(fun (r : permission_r) ->
9292+ r.permission_suggestions)
9393+ |> Jsont.Object.opt_mem "blockedPath" Jsont.string ~enc:(fun (r : permission_r) ->
9494+ r.blocked_path)
9595+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : permission_r) -> r.unknown)
9696+ |> Jsont.Object.finish)
9797+9898+ let initialize_jsont : initialize_r Jsont.t =
9999+ (* The hooks field is an object with string keys and json values *)
100100+ let hooks_map_jsont = Jsont.Object.as_string_map Jsont.json in
101101+ let module StringMap = Map.Make (String) in
102102+ let hooks_jsont =
103103+ Jsont.map
104104+ ~dec:(fun m -> StringMap.bindings m)
105105+ ~enc:(fun l -> StringMap.of_seq (List.to_seq l))
106106+ hooks_map_jsont
107107+ in
108108+ let make hooks unknown = { hooks; unknown } in
109109+ (Jsont.Object.map ~kind:"Initialize" make
110110+ |> Jsont.Object.opt_mem "hooks" hooks_jsont ~enc:(fun (r : initialize_r) -> r.hooks)
111111+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : initialize_r) -> r.unknown)
112112+ |> Jsont.Object.finish)
113113+114114+ let set_permission_mode_jsont : set_permission_mode_r Jsont.t =
115115+ let make mode unknown = { mode; unknown } in
116116+ (Jsont.Object.map ~kind:"SetPermissionMode" make
117117+ |> Jsont.Object.mem "mode" Permissions.Mode.jsont ~enc:(fun (r : set_permission_mode_r) -> r.mode)
118118+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : set_permission_mode_r) -> r.unknown)
119119+ |> Jsont.Object.finish)
120120+121121+ let hook_callback_jsont : hook_callback_r Jsont.t =
122122+ let make callback_id input tool_use_id unknown =
123123+ { callback_id; input; tool_use_id; unknown }
124124+ in
125125+ (Jsont.Object.map ~kind:"HookCallback" make
126126+ |> Jsont.Object.mem "callbackId" Jsont.string ~enc:(fun (r : hook_callback_r) -> r.callback_id)
127127+ |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : hook_callback_r) -> r.input)
128128+ |> Jsont.Object.opt_mem "toolUseId" Jsont.string ~enc:(fun (r : hook_callback_r) ->
129129+ r.tool_use_id)
130130+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : hook_callback_r) -> r.unknown)
131131+ |> Jsont.Object.finish)
132132+133133+ let mcp_message_jsont : mcp_message_r Jsont.t =
134134+ let make server_name message unknown = { server_name; message; unknown } in
135135+ (Jsont.Object.map ~kind:"McpMessage" make
136136+ |> Jsont.Object.mem "serverName" Jsont.string ~enc:(fun (r : mcp_message_r) -> r.server_name)
137137+ |> Jsont.Object.mem "message" Jsont.json ~enc:(fun (r : mcp_message_r) -> r.message)
138138+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : mcp_message_r) -> r.unknown)
139139+ |> Jsont.Object.finish)
140140+141141+ let set_model_jsont : set_model_r Jsont.t =
142142+ let make model unknown = { model; unknown } in
143143+ (Jsont.Object.map ~kind:"SetModel" make
144144+ |> Jsont.Object.mem "model" Jsont.string ~enc:(fun (r : set_model_r) -> r.model)
145145+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : set_model_r) -> r.unknown)
146146+ |> Jsont.Object.finish)
147147+148148+ let get_server_info_jsont : unit Jsont.t =
149149+ (Jsont.Object.map ~kind:"GetServerInfo" (fun _unknown -> ())
150150+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun () -> Unknown.empty)
151151+ |> Jsont.Object.finish)
152152+153153+ (* Main variant codec using subtype discriminator *)
154154+ let jsont : t Jsont.t =
155155+ let case_interrupt =
156156+ Jsont.Object.Case.map "interrupt" interrupt_jsont ~dec:(fun () ->
157157+ Interrupt)
158158+ in
159159+ let case_permission =
160160+ Jsont.Object.Case.map "canUseTool" permission_jsont ~dec:(fun v ->
161161+ Permission v)
162162+ in
163163+ let case_initialize =
164164+ Jsont.Object.Case.map "initialize" initialize_jsont ~dec:(fun v ->
165165+ Initialize v)
166166+ in
167167+ let case_set_permission_mode =
168168+ Jsont.Object.Case.map "setPermissionMode" set_permission_mode_jsont
169169+ ~dec:(fun v -> Set_permission_mode v)
170170+ in
171171+ let case_hook_callback =
172172+ Jsont.Object.Case.map "hookCallback" hook_callback_jsont ~dec:(fun v ->
173173+ Hook_callback v)
174174+ in
175175+ let case_mcp_message =
176176+ Jsont.Object.Case.map "mcpMessage" mcp_message_jsont ~dec:(fun v ->
177177+ Mcp_message v)
178178+ in
179179+ let case_set_model =
180180+ Jsont.Object.Case.map "setModel" set_model_jsont ~dec:(fun v ->
181181+ Set_model v)
182182+ in
183183+ let case_get_server_info =
184184+ Jsont.Object.Case.map "getServerInfo" get_server_info_jsont
185185+ ~dec:(fun () -> Get_server_info)
186186+ in
187187+188188+ let enc_case = function
189189+ | Interrupt -> Jsont.Object.Case.value case_interrupt ()
190190+ | Permission v -> Jsont.Object.Case.value case_permission v
191191+ | Initialize v -> Jsont.Object.Case.value case_initialize v
192192+ | Set_permission_mode v ->
193193+ Jsont.Object.Case.value case_set_permission_mode v
194194+ | Hook_callback v -> Jsont.Object.Case.value case_hook_callback v
195195+ | Mcp_message v -> Jsont.Object.Case.value case_mcp_message v
196196+ | Set_model v -> Jsont.Object.Case.value case_set_model v
197197+ | Get_server_info -> Jsont.Object.Case.value case_get_server_info ()
198198+ in
199199+200200+ let cases =
201201+ Jsont.Object.Case.
202202+ [
203203+ make case_interrupt;
204204+ make case_permission;
205205+ make case_initialize;
206206+ make case_set_permission_mode;
207207+ make case_hook_callback;
208208+ make case_mcp_message;
209209+ make case_set_model;
210210+ make case_get_server_info;
211211+ ]
212212+ in
213213+214214+ Jsont.Object.map ~kind:"Request" Fun.id
215215+ |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
216216+ ~tag_to_string:Fun.id ~tag_compare:String.compare
217217+ |> Jsont.Object.finish
218218+end
219219+220220+module Response = struct
221221+ (* Individual record types for each response variant *)
222222+ type success_r = {
223223+ request_id : string;
224224+ response : Jsont.json option;
225225+ unknown : Unknown.t;
226226+ }
227227+228228+ type error_r = {
229229+ request_id : string;
230230+ error : string;
231231+ unknown : Unknown.t;
232232+ }
233233+234234+ type t = Success of success_r | Error of error_r
235235+236236+ let success ~request_id ?response () =
237237+ Success { request_id; response; unknown = Unknown.empty }
238238+239239+ let error ~request_id ~error () =
240240+ Error { request_id; error; unknown = Unknown.empty }
241241+242242+ (* Individual record codecs *)
243243+ let success_jsont : success_r Jsont.t =
244244+ let make request_id response unknown = { request_id; response; unknown } in
245245+ (Jsont.Object.map ~kind:"Success" make
246246+ |> Jsont.Object.mem "requestId" Jsont.string ~enc:(fun (r : success_r) -> r.request_id)
247247+ |> Jsont.Object.opt_mem "response" Jsont.json ~enc:(fun (r : success_r) -> r.response)
248248+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : success_r) -> r.unknown)
249249+ |> Jsont.Object.finish)
250250+251251+ let error_jsont : error_r Jsont.t =
252252+ let make request_id error unknown = { request_id; error; unknown } in
253253+ (Jsont.Object.map ~kind:"Error" make
254254+ |> Jsont.Object.mem "requestId" Jsont.string ~enc:(fun (r : error_r) -> r.request_id)
255255+ |> Jsont.Object.mem "error" Jsont.string ~enc:(fun (r : error_r) -> r.error)
256256+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : error_r) -> r.unknown)
257257+ |> Jsont.Object.finish)
258258+259259+ (* Main variant codec using subtype discriminator *)
260260+ let jsont : t Jsont.t =
261261+ let case_success =
262262+ Jsont.Object.Case.map "success" success_jsont ~dec:(fun v -> Success v)
263263+ in
264264+ let case_error =
265265+ Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v)
266266+ in
267267+268268+ let enc_case = function
269269+ | Success v -> Jsont.Object.Case.value case_success v
270270+ | Error v -> Jsont.Object.Case.value case_error v
271271+ in
272272+273273+ let cases = Jsont.Object.Case.[ make case_success; make case_error ] in
274274+275275+ Jsont.Object.map ~kind:"Response" Fun.id
276276+ |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
277277+ ~tag_to_string:Fun.id ~tag_compare:String.compare
278278+ |> Jsont.Object.finish
279279+end
280280+281281+type request_envelope = {
282282+ request_id : string;
283283+ request : Request.t;
284284+ unknown : Unknown.t;
285285+}
286286+287287+type response_envelope = { response : Response.t; unknown : Unknown.t }
288288+289289+let create_request ~request_id ~request () =
290290+ { request_id; request; unknown = Unknown.empty }
291291+292292+let create_response ~response () = { response; unknown = Unknown.empty }
293293+294294+(* Envelope codecs *)
295295+let request_envelope_jsont : request_envelope Jsont.t =
296296+ let make request_id request unknown = { request_id; request; unknown } in
297297+ (Jsont.Object.map ~kind:"RequestEnvelope" make
298298+ |> Jsont.Object.mem "requestId" Jsont.string ~enc:(fun (r : request_envelope) -> r.request_id)
299299+ |> Jsont.Object.mem "request" Request.jsont ~enc:(fun (r : request_envelope) -> r.request)
300300+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : request_envelope) -> r.unknown)
301301+ |> Jsont.Object.finish)
302302+303303+let response_envelope_jsont : response_envelope Jsont.t =
304304+ let make response unknown = { response; unknown } in
305305+ (Jsont.Object.map ~kind:"ResponseEnvelope" make
306306+ |> Jsont.Object.mem "response" Response.jsont ~enc:(fun (r : response_envelope) -> r.response)
307307+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : response_envelope) -> r.unknown)
308308+ |> Jsont.Object.finish)
309309+310310+(** Server information *)
311311+module Server_info = struct
312312+ type t = {
313313+ version : string;
314314+ capabilities : string list;
315315+ commands : string list;
316316+ output_styles : string list;
317317+ unknown : Unknown.t;
318318+ }
319319+320320+ let create ~version ~capabilities ~commands ~output_styles () =
321321+ { version; capabilities; commands; output_styles; unknown = Unknown.empty }
322322+323323+ let version t = t.version
324324+ let capabilities t = t.capabilities
325325+ let commands t = t.commands
326326+ let output_styles t = t.output_styles
327327+ let unknown t = t.unknown
328328+329329+ let jsont : t Jsont.t =
330330+ let make version capabilities commands output_styles unknown =
331331+ { version; capabilities; commands; output_styles; unknown }
332332+ in
333333+ Jsont.Object.map ~kind:"ServerInfo" make
334334+ |> Jsont.Object.mem "version" Jsont.string ~enc:(fun r -> r.version)
335335+ |> Jsont.Object.mem "capabilities" (Jsont.list Jsont.string)
336336+ ~enc:(fun r -> r.capabilities)
337337+ ~dec_absent:[]
338338+ |> Jsont.Object.mem "commands" (Jsont.list Jsont.string)
339339+ ~enc:(fun r -> r.commands)
340340+ ~dec_absent:[]
341341+ |> Jsont.Object.mem "outputStyles" (Jsont.list Jsont.string)
342342+ ~enc:(fun r -> r.output_styles)
343343+ ~dec_absent:[]
344344+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun r -> r.unknown)
345345+ |> Jsont.Object.finish
346346+end
+197
proto/control.mli
···11+(** Control protocol wire format for SDK communication.
22+33+ This module defines the wire format for the SDK control protocol used for
44+ bidirectional communication between the SDK and the Claude CLI. It handles
55+ JSON serialization and deserialization of control messages.
66+77+ The control protocol enables:
88+ - Permission requests for tool usage authorization
99+ - Hook callbacks for intercepting and modifying tool execution
1010+ - Dynamic control for changing settings mid-conversation
1111+ - Server introspection for querying capabilities *)
1212+1313+(** {1 Request Types} *)
1414+1515+module Request : sig
1616+ (** SDK control request types. *)
1717+1818+ type permission_r = private {
1919+ tool_name : string;
2020+ input : Jsont.json;
2121+ permission_suggestions : Permissions.Update.t list option;
2222+ blocked_path : string option;
2323+ unknown : Unknown.t;
2424+ }
2525+2626+ type initialize_r = private {
2727+ hooks : (string * Jsont.json) list option;
2828+ unknown : Unknown.t;
2929+ }
3030+3131+ type set_permission_mode_r = private {
3232+ mode : Permissions.Mode.t;
3333+ unknown : Unknown.t;
3434+ }
3535+3636+ type hook_callback_r = private {
3737+ callback_id : string;
3838+ input : Jsont.json;
3939+ tool_use_id : string option;
4040+ unknown : Unknown.t;
4141+ }
4242+4343+ type mcp_message_r = private {
4444+ server_name : string;
4545+ message : Jsont.json;
4646+ unknown : Unknown.t;
4747+ }
4848+4949+ type set_model_r = private { model : string; unknown : Unknown.t }
5050+5151+ type t =
5252+ | Interrupt
5353+ | Permission of permission_r
5454+ | Initialize of initialize_r
5555+ | Set_permission_mode of set_permission_mode_r
5656+ | Hook_callback of hook_callback_r
5757+ | Mcp_message of mcp_message_r
5858+ | Set_model of set_model_r
5959+ | Get_server_info
6060+ (** The type of SDK control requests. Wire format uses "subtype" field:
6161+ "interrupt", "canUseTool", "initialize", "setPermissionMode",
6262+ "hookCallback", "mcpMessage", "setModel", "getServerInfo". *)
6363+6464+ val jsont : t Jsont.t
6565+ (** [jsont] is the Jsont codec for requests. *)
6666+6767+ val interrupt : unit -> t
6868+ (** [interrupt ()] creates an interrupt request. *)
6969+7070+ val permission :
7171+ tool_name:string ->
7272+ input:Jsont.json ->
7373+ ?permission_suggestions:Permissions.Update.t list ->
7474+ ?blocked_path:string ->
7575+ unit ->
7676+ t
7777+ (** [permission ~tool_name ~input ?permission_suggestions ?blocked_path ()]
7878+ creates a permission request. *)
7979+8080+ val initialize : ?hooks:(string * Jsont.json) list -> unit -> t
8181+ (** [initialize ?hooks ()] creates an initialize request. *)
8282+8383+ val set_permission_mode : mode:Permissions.Mode.t -> unit -> t
8484+ (** [set_permission_mode ~mode ()] creates a permission mode change request.
8585+ *)
8686+8787+ val hook_callback :
8888+ callback_id:string ->
8989+ input:Jsont.json ->
9090+ ?tool_use_id:string ->
9191+ unit ->
9292+ t
9393+ (** [hook_callback ~callback_id ~input ?tool_use_id ()] creates a hook
9494+ callback request. *)
9595+9696+ val mcp_message : server_name:string -> message:Jsont.json -> unit -> t
9797+ (** [mcp_message ~server_name ~message ()] creates an MCP message request. *)
9898+9999+ val set_model : model:string -> unit -> t
100100+ (** [set_model ~model ()] creates a model change request. *)
101101+102102+ val get_server_info : unit -> t
103103+ (** [get_server_info ()] creates a server info request. *)
104104+end
105105+106106+(** {1 Response Types} *)
107107+108108+module Response : sig
109109+ (** SDK control response types. *)
110110+111111+ type success_r = private {
112112+ request_id : string;
113113+ response : Jsont.json option;
114114+ unknown : Unknown.t;
115115+ }
116116+117117+ type error_r = private {
118118+ request_id : string;
119119+ error : string;
120120+ unknown : Unknown.t;
121121+ }
122122+123123+ type t = Success of success_r | Error of error_r
124124+ (** The type of SDK control responses. Wire format uses "subtype" field:
125125+ "success", "error". *)
126126+127127+ val jsont : t Jsont.t
128128+ (** [jsont] is the Jsont codec for responses. *)
129129+130130+ val success : request_id:string -> ?response:Jsont.json -> unit -> t
131131+ (** [success ~request_id ?response ()] creates a success response. *)
132132+133133+ val error : request_id:string -> error:string -> unit -> t
134134+ (** [error ~request_id ~error ()] creates an error response. *)
135135+end
136136+137137+(** {1 Control Envelopes} *)
138138+139139+type request_envelope = {
140140+ request_id : string;
141141+ request : Request.t;
142142+ unknown : Unknown.t;
143143+}
144144+(** Control request envelope. Wire format has "type": "control_request". *)
145145+146146+type response_envelope = { response : Response.t; unknown : Unknown.t }
147147+(** Control response envelope. Wire format has "type": "control_response". *)
148148+149149+val request_envelope_jsont : request_envelope Jsont.t
150150+(** [request_envelope_jsont] is the Jsont codec for request envelopes. *)
151151+152152+val response_envelope_jsont : response_envelope Jsont.t
153153+(** [response_envelope_jsont] is the Jsont codec for response envelopes. *)
154154+155155+val create_request : request_id:string -> request:Request.t -> unit -> request_envelope
156156+(** [create_request ~request_id ~request ()] creates a control request envelope.
157157+*)
158158+159159+val create_response : response:Response.t -> unit -> response_envelope
160160+(** [create_response ~response ()] creates a control response envelope. *)
161161+162162+(** {1 Server Information} *)
163163+164164+module Server_info : sig
165165+ (** Server information and capabilities. *)
166166+167167+ type t
168168+ (** Server metadata and capabilities. *)
169169+170170+ val jsont : t Jsont.t
171171+ (** [jsont] is the Jsont codec for server info. *)
172172+173173+ val create :
174174+ version:string ->
175175+ capabilities:string list ->
176176+ commands:string list ->
177177+ output_styles:string list ->
178178+ unit ->
179179+ t
180180+ (** [create ~version ~capabilities ~commands ~output_styles ()] creates
181181+ server info. *)
182182+183183+ val version : t -> string
184184+ (** [version t] returns the server version. *)
185185+186186+ val capabilities : t -> string list
187187+ (** [capabilities t] returns the server capabilities. *)
188188+189189+ val commands : t -> string list
190190+ (** [commands t] returns available commands. *)
191191+192192+ val output_styles : t -> string list
193193+ (** [output_styles t] returns available output styles. *)
194194+195195+ val unknown : t -> Unknown.t
196196+ (** [unknown t] returns the unknown fields. *)
197197+end
···11+(** Claude Code Hooks System - Wire Format
22+33+ This module defines the wire format for hook configuration. *)
44+55+(** Hook events that can be intercepted *)
66+type event =
77+ | Pre_tool_use
88+ | Post_tool_use
99+ | User_prompt_submit
1010+ | Stop
1111+ | Subagent_stop
1212+ | Pre_compact
1313+1414+let event_to_string = function
1515+ | Pre_tool_use -> "PreToolUse"
1616+ | Post_tool_use -> "PostToolUse"
1717+ | User_prompt_submit -> "UserPromptSubmit"
1818+ | Stop -> "Stop"
1919+ | Subagent_stop -> "SubagentStop"
2020+ | Pre_compact -> "PreCompact"
2121+2222+let event_of_string = function
2323+ | "PreToolUse" -> Pre_tool_use
2424+ | "PostToolUse" -> Post_tool_use
2525+ | "UserPromptSubmit" -> User_prompt_submit
2626+ | "Stop" -> Stop
2727+ | "SubagentStop" -> Subagent_stop
2828+ | "PreCompact" -> Pre_compact
2929+ | s -> raise (Invalid_argument (Printf.sprintf "Unknown hook event: %s" s))
3030+3131+let event_jsont : event Jsont.t =
3232+ Jsont.enum
3333+ [
3434+ ("PreToolUse", Pre_tool_use);
3535+ ("PostToolUse", Post_tool_use);
3636+ ("UserPromptSubmit", User_prompt_submit);
3737+ ("Stop", Stop);
3838+ ("SubagentStop", Subagent_stop);
3939+ ("PreCompact", Pre_compact);
4040+ ]
4141+4242+(** Context provided to hook callbacks *)
4343+module Context = struct
4444+ type t = { signal : unit option; unknown : Unknown.t }
4545+4646+ let create ?signal () =
4747+ let signal = Option.map (fun () -> ()) signal in
4848+ { signal; unknown = Unknown.empty }
4949+5050+ let signal t = t.signal
5151+ let unknown t = t.unknown
5252+5353+ let jsont : t Jsont.t =
5454+ let make unknown = { signal = None; unknown } in
5555+ Jsont.Object.map ~kind:"Context" make
5656+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
5757+ |> Jsont.Object.finish
5858+end
5959+6060+(** Hook decision control *)
6161+type decision = Continue | Block
6262+6363+let decision_jsont : decision Jsont.t =
6464+ Jsont.enum [ ("continue", Continue); ("block", Block) ]
6565+6666+(** Generic hook result *)
6767+type result = {
6868+ decision : decision option;
6969+ system_message : string option;
7070+ hook_specific_output : Jsont.json option;
7171+ unknown : Unknown.t;
7272+}
7373+7474+let result_jsont : result Jsont.t =
7575+ let make decision system_message hook_specific_output unknown =
7676+ { decision; system_message; hook_specific_output; unknown }
7777+ in
7878+ Jsont.Object.map ~kind:"Result" make
7979+ |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun r -> r.decision)
8080+ |> Jsont.Object.opt_mem "systemMessage" Jsont.string ~enc:(fun r ->
8181+ r.system_message)
8282+ |> Jsont.Object.opt_mem "hookSpecificOutput" Jsont.json ~enc:(fun r ->
8383+ r.hook_specific_output)
8484+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun r -> r.unknown)
8585+ |> Jsont.Object.finish
8686+8787+(** {1 PreToolUse Hook} *)
8888+module PreToolUse = struct
8989+ module Input = struct
9090+ type t = {
9191+ session_id : string;
9292+ transcript_path : string;
9393+ tool_name : string;
9494+ tool_input : Jsont.json;
9595+ unknown : Unknown.t;
9696+ }
9797+9898+ let session_id t = t.session_id
9999+ let transcript_path t = t.transcript_path
100100+ let tool_name t = t.tool_name
101101+ let tool_input t = t.tool_input
102102+ let unknown t = t.unknown
103103+104104+ let jsont : t Jsont.t =
105105+ let make session_id transcript_path tool_name tool_input unknown =
106106+ { session_id; transcript_path; tool_name; tool_input; unknown }
107107+ in
108108+ Jsont.Object.map ~kind:"PreToolUseInput" make
109109+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
110110+ |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
111111+ |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
112112+ |> Jsont.Object.mem "tool_input" Jsont.json ~enc:tool_input
113113+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
114114+ |> Jsont.Object.finish
115115+ end
116116+117117+ type permission_decision = [ `Allow | `Deny | `Ask ]
118118+119119+ let permission_decision_jsont : permission_decision Jsont.t =
120120+ Jsont.enum [ ("allow", `Allow); ("deny", `Deny); ("ask", `Ask) ]
121121+122122+ module Output = struct
123123+ type t = {
124124+ permission_decision : permission_decision option;
125125+ permission_decision_reason : string option;
126126+ updated_input : Jsont.json option;
127127+ unknown : Unknown.t;
128128+ }
129129+130130+ let jsont : t Jsont.t =
131131+ let make _hook_event_name permission_decision permission_decision_reason
132132+ updated_input unknown =
133133+ {
134134+ permission_decision;
135135+ permission_decision_reason;
136136+ updated_input;
137137+ unknown;
138138+ }
139139+ in
140140+ Jsont.Object.map ~kind:"PreToolUseOutput" make
141141+ |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun _ ->
142142+ "PreToolUse")
143143+ |> Jsont.Object.opt_mem "permissionDecision" permission_decision_jsont
144144+ ~enc:(fun o -> o.permission_decision)
145145+ |> Jsont.Object.opt_mem "permissionDecisionReason" Jsont.string
146146+ ~enc:(fun o -> o.permission_decision_reason)
147147+ |> Jsont.Object.opt_mem "updatedInput" Jsont.json ~enc:(fun o ->
148148+ o.updated_input)
149149+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun o -> o.unknown)
150150+ |> Jsont.Object.finish
151151+152152+ let allow ?reason ?updated_input () =
153153+ {
154154+ permission_decision = Some `Allow;
155155+ permission_decision_reason = reason;
156156+ updated_input;
157157+ unknown = Unknown.empty;
158158+ }
159159+160160+ let deny ?reason () =
161161+ {
162162+ permission_decision = Some `Deny;
163163+ permission_decision_reason = reason;
164164+ updated_input = None;
165165+ unknown = Unknown.empty;
166166+ }
167167+168168+ let ask ?reason () =
169169+ {
170170+ permission_decision = Some `Ask;
171171+ permission_decision_reason = reason;
172172+ updated_input = None;
173173+ unknown = Unknown.empty;
174174+ }
175175+176176+ let continue () =
177177+ {
178178+ permission_decision = None;
179179+ permission_decision_reason = None;
180180+ updated_input = None;
181181+ unknown = Unknown.empty;
182182+ }
183183+ end
184184+end
185185+186186+(** {1 PostToolUse Hook} *)
187187+module PostToolUse = struct
188188+ module Input = struct
189189+ type t = {
190190+ session_id : string;
191191+ transcript_path : string;
192192+ tool_name : string;
193193+ tool_input : Jsont.json;
194194+ tool_response : Jsont.json;
195195+ unknown : Unknown.t;
196196+ }
197197+198198+ let session_id t = t.session_id
199199+ let transcript_path t = t.transcript_path
200200+ let tool_name t = t.tool_name
201201+ let tool_input t = t.tool_input
202202+ let tool_response t = t.tool_response
203203+ let unknown t = t.unknown
204204+205205+ let jsont : t Jsont.t =
206206+ let make session_id transcript_path tool_name tool_input tool_response
207207+ unknown =
208208+ {
209209+ session_id;
210210+ transcript_path;
211211+ tool_name;
212212+ tool_input;
213213+ tool_response;
214214+ unknown;
215215+ }
216216+ in
217217+ Jsont.Object.map ~kind:"PostToolUseInput" make
218218+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
219219+ |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
220220+ |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
221221+ |> Jsont.Object.mem "tool_input" Jsont.json ~enc:tool_input
222222+ |> Jsont.Object.mem "tool_response" Jsont.json ~enc:tool_response
223223+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
224224+ |> Jsont.Object.finish
225225+ end
226226+227227+ module Output = struct
228228+ type t = {
229229+ decision : decision option;
230230+ reason : string option;
231231+ additional_context : string option;
232232+ unknown : Unknown.t;
233233+ }
234234+235235+ let jsont : t Jsont.t =
236236+ let make _hook_event_name decision reason additional_context unknown =
237237+ { decision; reason; additional_context; unknown }
238238+ in
239239+ Jsont.Object.map ~kind:"PostToolUseOutput" make
240240+ |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun _ ->
241241+ "PostToolUse")
242242+ |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o -> o.decision)
243243+ |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
244244+ |> Jsont.Object.opt_mem "additionalContext" Jsont.string ~enc:(fun o ->
245245+ o.additional_context)
246246+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun o -> o.unknown)
247247+ |> Jsont.Object.finish
248248+249249+ let continue ?additional_context () =
250250+ {
251251+ decision = None;
252252+ reason = None;
253253+ additional_context;
254254+ unknown = Unknown.empty;
255255+ }
256256+257257+ let block ?reason ?additional_context () =
258258+ {
259259+ decision = Some Block;
260260+ reason;
261261+ additional_context;
262262+ unknown = Unknown.empty;
263263+ }
264264+ end
265265+end
266266+267267+(** {1 UserPromptSubmit Hook} *)
268268+module UserPromptSubmit = struct
269269+ module Input = struct
270270+ type t = {
271271+ session_id : string;
272272+ transcript_path : string;
273273+ prompt : string;
274274+ unknown : Unknown.t;
275275+ }
276276+277277+ let session_id t = t.session_id
278278+ let transcript_path t = t.transcript_path
279279+ let prompt t = t.prompt
280280+ let unknown t = t.unknown
281281+282282+ let jsont : t Jsont.t =
283283+ let make session_id transcript_path prompt unknown =
284284+ { session_id; transcript_path; prompt; unknown }
285285+ in
286286+ Jsont.Object.map ~kind:"UserPromptSubmitInput" make
287287+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
288288+ |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
289289+ |> Jsont.Object.mem "prompt" Jsont.string ~enc:prompt
290290+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
291291+ |> Jsont.Object.finish
292292+ end
293293+294294+ module Output = struct
295295+ type t = {
296296+ decision : decision option;
297297+ reason : string option;
298298+ additional_context : string option;
299299+ unknown : Unknown.t;
300300+ }
301301+302302+ let jsont : t Jsont.t =
303303+ let make _hook_event_name decision reason additional_context unknown =
304304+ { decision; reason; additional_context; unknown }
305305+ in
306306+ Jsont.Object.map ~kind:"UserPromptSubmitOutput" make
307307+ |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun _ ->
308308+ "UserPromptSubmit")
309309+ |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o -> o.decision)
310310+ |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
311311+ |> Jsont.Object.opt_mem "additionalContext" Jsont.string ~enc:(fun o ->
312312+ o.additional_context)
313313+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun o -> o.unknown)
314314+ |> Jsont.Object.finish
315315+316316+ let continue ?additional_context () =
317317+ {
318318+ decision = None;
319319+ reason = None;
320320+ additional_context;
321321+ unknown = Unknown.empty;
322322+ }
323323+324324+ let block ?reason () =
325325+ {
326326+ decision = Some Block;
327327+ reason;
328328+ additional_context = None;
329329+ unknown = Unknown.empty;
330330+ }
331331+ end
332332+end
333333+334334+(** {1 Stop Hook} *)
335335+module Stop = struct
336336+ module Input = struct
337337+ type t = {
338338+ session_id : string;
339339+ transcript_path : string;
340340+ stop_hook_active : bool;
341341+ unknown : Unknown.t;
342342+ }
343343+344344+ let session_id t = t.session_id
345345+ let transcript_path t = t.transcript_path
346346+ let stop_hook_active t = t.stop_hook_active
347347+ let unknown t = t.unknown
348348+349349+ let jsont : t Jsont.t =
350350+ let make session_id transcript_path stop_hook_active unknown =
351351+ { session_id; transcript_path; stop_hook_active; unknown }
352352+ in
353353+ Jsont.Object.map ~kind:"StopInput" make
354354+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
355355+ |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
356356+ |> Jsont.Object.mem "stop_hook_active" Jsont.bool ~enc:stop_hook_active
357357+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
358358+ |> Jsont.Object.finish
359359+ end
360360+361361+ module Output = struct
362362+ type t = {
363363+ decision : decision option;
364364+ reason : string option;
365365+ unknown : Unknown.t;
366366+ }
367367+368368+ let jsont : t Jsont.t =
369369+ let make _hook_event_name decision reason unknown =
370370+ { decision; reason; unknown }
371371+ in
372372+ Jsont.Object.map ~kind:"StopOutput" make
373373+ |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun _ -> "Stop")
374374+ |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o -> o.decision)
375375+ |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
376376+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun o -> o.unknown)
377377+ |> Jsont.Object.finish
378378+379379+ let continue () =
380380+ { decision = None; reason = None; unknown = Unknown.empty }
381381+382382+ let block ?reason () =
383383+ { decision = Some Block; reason; unknown = Unknown.empty }
384384+ end
385385+end
386386+387387+(** {1 SubagentStop Hook} - Same structure as Stop *)
388388+module SubagentStop = struct
389389+ module Input = struct
390390+ type t = Stop.Input.t
391391+392392+ let jsont = Stop.Input.jsont
393393+ let session_id = Stop.Input.session_id
394394+ let transcript_path = Stop.Input.transcript_path
395395+ let stop_hook_active = Stop.Input.stop_hook_active
396396+ let unknown = Stop.Input.unknown
397397+ end
398398+399399+ module Output = struct
400400+ type t = Stop.Output.t
401401+402402+ let jsont : t Jsont.t =
403403+ let make _hook_event_name decision reason unknown : t =
404404+ { Stop.Output.decision; reason; unknown }
405405+ in
406406+ Jsont.Object.map ~kind:"SubagentStopOutput" make
407407+ |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun _ ->
408408+ "SubagentStop")
409409+ |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun (o : t) ->
410410+ o.Stop.Output.decision)
411411+ |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun (o : t) ->
412412+ o.Stop.Output.reason)
413413+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (o : t) ->
414414+ o.Stop.Output.unknown)
415415+ |> Jsont.Object.finish
416416+417417+ let continue = Stop.Output.continue
418418+ let block = Stop.Output.block
419419+ end
420420+end
421421+422422+(** {1 PreCompact Hook} *)
423423+module PreCompact = struct
424424+ module Input = struct
425425+ type t = {
426426+ session_id : string;
427427+ transcript_path : string;
428428+ unknown : Unknown.t;
429429+ }
430430+431431+ let session_id t = t.session_id
432432+ let transcript_path t = t.transcript_path
433433+ let unknown t = t.unknown
434434+435435+ let jsont : t Jsont.t =
436436+ let make session_id transcript_path unknown =
437437+ { session_id; transcript_path; unknown }
438438+ in
439439+ Jsont.Object.map ~kind:"PreCompactInput" make
440440+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
441441+ |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
442442+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
443443+ |> Jsont.Object.finish
444444+ end
445445+446446+ module Output = struct
447447+ type t = unit
448448+449449+ let jsont : t Jsont.t =
450450+ Jsont.Object.map ~kind:"PreCompactOutput" (fun _hook_event_name -> ())
451451+ |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun () ->
452452+ "PreCompact")
453453+ |> Jsont.Object.finish
454454+455455+ let continue () = ()
456456+ end
457457+end
458458+459459+(** {1 Result Builders} *)
460460+let continue ?system_message ?hook_specific_output () =
461461+ {
462462+ decision = None;
463463+ system_message;
464464+ hook_specific_output;
465465+ unknown = Unknown.empty;
466466+ }
467467+468468+let block ?system_message ?hook_specific_output () =
469469+ {
470470+ decision = Some Block;
471471+ system_message;
472472+ hook_specific_output;
473473+ unknown = Unknown.empty;
474474+ }
+359
proto/hooks.mli
···11+(** Claude Code Hooks System - Wire Format
22+33+ This module defines the wire format for hook configuration. Hooks allow you
44+ to intercept and control events in Claude Code sessions, such as tool usage,
55+ prompt submission, and session stops.
66+77+ {1 Overview}
88+99+ Hooks are organized by event type, with each event having:
1010+ - A typed input structure (accessible via submodules)
1111+ - A typed output structure for responses
1212+ - Helper functions for common responses
1313+1414+ This is the wire format module - it does not include the callback system or
1515+ Eio dependencies. For the full hooks system with callbacks, see the
1616+ [Hooks] module in the [lib] directory. *)
1717+1818+(** {1 Hook Events} *)
1919+2020+type event =
2121+ | Pre_tool_use (** Fires before a tool is executed *)
2222+ | Post_tool_use (** Fires after a tool completes *)
2323+ | User_prompt_submit (** Fires when user submits a prompt *)
2424+ | Stop (** Fires when conversation stops *)
2525+ | Subagent_stop (** Fires when a subagent stops *)
2626+ | Pre_compact (** Fires before message compaction *)
2727+(** Hook event types *)
2828+2929+val event_to_string : event -> string
3030+(** [event_to_string event] converts an event to its wire format string.
3131+ Wire format: "PreToolUse", "PostToolUse", "UserPromptSubmit", "Stop",
3232+ "SubagentStop", "PreCompact" *)
3333+3434+val event_of_string : string -> event
3535+(** [event_of_string s] parses an event from its wire format string.
3636+ @raise Invalid_argument if the string is not a valid event. *)
3737+3838+val event_jsont : event Jsont.t
3939+(** [event_jsont] is the Jsont codec for hook events. *)
4040+4141+(** {1 Context} *)
4242+4343+module Context : sig
4444+ (** Context provided to hook callbacks. *)
4545+4646+ type t
4747+ (** The type of hook context. *)
4848+4949+ val jsont : t Jsont.t
5050+ (** [jsont] is the Jsont codec for hook context. Preserves unknown fields. *)
5151+5252+ val create : ?signal:unit -> unit -> t
5353+ (** [create ?signal ()] creates a new context.
5454+ @param signal Optional abort signal support (future use) *)
5555+5656+ val signal : t -> unit option
5757+ (** [signal t] returns the optional abort signal. *)
5858+5959+ val unknown : t -> Unknown.t
6060+ (** [unknown t] returns the unknown fields. *)
6161+end
6262+6363+(** {1 Decisions} *)
6464+6565+type decision =
6666+ | Continue (** Allow the action to proceed *)
6767+ | Block (** Block the action *)
6868+(** Hook decision control *)
6969+7070+val decision_jsont : decision Jsont.t
7171+(** [decision_jsont] is the Jsont codec for hook decisions.
7272+ Wire format: "continue", "block" *)
7373+7474+(** {1 Typed Hook Modules} *)
7575+7676+(** PreToolUse hook - fires before tool execution *)
7777+module PreToolUse : sig
7878+ (** {2 Input} *)
7979+8080+ module Input : sig
8181+ type t
8282+ (** Typed input for PreToolUse hooks *)
8383+8484+ val jsont : t Jsont.t
8585+ (** [jsont] is the Jsont codec for PreToolUse input. *)
8686+8787+ val session_id : t -> string
8888+ (** [session_id t] returns the session ID. *)
8989+9090+ val transcript_path : t -> string
9191+ (** [transcript_path t] returns the transcript file path. *)
9292+9393+ val tool_name : t -> string
9494+ (** [tool_name t] returns the tool name being invoked. *)
9595+9696+ val tool_input : t -> Jsont.json
9797+ (** [tool_input t] returns the tool's input as raw JSON. *)
9898+9999+ val unknown : t -> Unknown.t
100100+ (** [unknown t] returns the unknown fields. *)
101101+ end
102102+103103+ (** {2 Output} *)
104104+105105+ type permission_decision = [ `Allow | `Deny | `Ask ]
106106+ (** Permission decision for tool usage.
107107+ Wire format: "allow", "deny", "ask" *)
108108+109109+ val permission_decision_jsont : permission_decision Jsont.t
110110+ (** [permission_decision_jsont] is the Jsont codec for permission decisions. *)
111111+112112+ module Output : sig
113113+ type t
114114+ (** Typed output for PreToolUse hooks *)
115115+116116+ val jsont : t Jsont.t
117117+ (** [jsont] is the Jsont codec for PreToolUse output. *)
118118+119119+ val allow :
120120+ ?reason:string -> ?updated_input:Jsont.json -> unit -> t
121121+ (** [allow ?reason ?updated_input ()] creates an allow response.
122122+ @param reason Optional explanation for allowing
123123+ @param updated_input Optional modified tool input *)
124124+125125+ val deny : ?reason:string -> unit -> t
126126+ (** [deny ?reason ()] creates a deny response.
127127+ @param reason Optional explanation for denying *)
128128+129129+ val ask : ?reason:string -> unit -> t
130130+ (** [ask ?reason ()] creates an ask response to prompt the user.
131131+ @param reason Optional explanation for asking *)
132132+133133+ val continue : unit -> t
134134+ (** [continue ()] creates a continue response with no decision. *)
135135+ end
136136+end
137137+138138+(** PostToolUse hook - fires after tool execution *)
139139+module PostToolUse : sig
140140+ (** {2 Input} *)
141141+142142+ module Input : sig
143143+ type t
144144+ (** Typed input for PostToolUse hooks *)
145145+146146+ val jsont : t Jsont.t
147147+ (** [jsont] is the Jsont codec for PostToolUse input. *)
148148+149149+ val session_id : t -> string
150150+ (** [session_id t] returns the session ID. *)
151151+152152+ val transcript_path : t -> string
153153+ (** [transcript_path t] returns the transcript file path. *)
154154+155155+ val tool_name : t -> string
156156+ (** [tool_name t] returns the tool name that was invoked. *)
157157+158158+ val tool_input : t -> Jsont.json
159159+ (** [tool_input t] returns the tool's input as raw JSON. *)
160160+161161+ val tool_response : t -> Jsont.json
162162+ (** [tool_response t] returns the tool's response as raw JSON. *)
163163+164164+ val unknown : t -> Unknown.t
165165+ (** [unknown t] returns the unknown fields. *)
166166+ end
167167+168168+ (** {2 Output} *)
169169+170170+ module Output : sig
171171+ type t
172172+ (** Typed output for PostToolUse hooks *)
173173+174174+ val jsont : t Jsont.t
175175+ (** [jsont] is the Jsont codec for PostToolUse output. *)
176176+177177+ val continue : ?additional_context:string -> unit -> t
178178+ (** [continue ?additional_context ()] creates a continue response.
179179+ @param additional_context Optional context to add to the transcript *)
180180+181181+ val block : ?reason:string -> ?additional_context:string -> unit -> t
182182+ (** [block ?reason ?additional_context ()] creates a block response.
183183+ @param reason Optional explanation for blocking
184184+ @param additional_context Optional context to add to the transcript *)
185185+ end
186186+end
187187+188188+(** UserPromptSubmit hook - fires when user submits a prompt *)
189189+module UserPromptSubmit : sig
190190+ (** {2 Input} *)
191191+192192+ module Input : sig
193193+ type t
194194+ (** Typed input for UserPromptSubmit hooks *)
195195+196196+ val jsont : t Jsont.t
197197+ (** [jsont] is the Jsont codec for UserPromptSubmit input. *)
198198+199199+ val session_id : t -> string
200200+ (** [session_id t] returns the session ID. *)
201201+202202+ val transcript_path : t -> string
203203+ (** [transcript_path t] returns the transcript file path. *)
204204+205205+ val prompt : t -> string
206206+ (** [prompt t] returns the user's prompt text. *)
207207+208208+ val unknown : t -> Unknown.t
209209+ (** [unknown t] returns the unknown fields. *)
210210+ end
211211+212212+ (** {2 Output} *)
213213+214214+ module Output : sig
215215+ type t
216216+ (** Typed output for UserPromptSubmit hooks *)
217217+218218+ val jsont : t Jsont.t
219219+ (** [jsont] is the Jsont codec for UserPromptSubmit output. *)
220220+221221+ val continue : ?additional_context:string -> unit -> t
222222+ (** [continue ?additional_context ()] creates a continue response.
223223+ @param additional_context Optional context to add to the transcript *)
224224+225225+ val block : ?reason:string -> unit -> t
226226+ (** [block ?reason ()] creates a block response.
227227+ @param reason Optional explanation for blocking *)
228228+ end
229229+end
230230+231231+(** Stop hook - fires when conversation stops *)
232232+module Stop : sig
233233+ (** {2 Input} *)
234234+235235+ module Input : sig
236236+ type t
237237+ (** Typed input for Stop hooks *)
238238+239239+ val jsont : t Jsont.t
240240+ (** [jsont] is the Jsont codec for Stop input. *)
241241+242242+ val session_id : t -> string
243243+ (** [session_id t] returns the session ID. *)
244244+245245+ val transcript_path : t -> string
246246+ (** [transcript_path t] returns the transcript file path. *)
247247+248248+ val stop_hook_active : t -> bool
249249+ (** [stop_hook_active t] returns whether stop hooks are active. *)
250250+251251+ val unknown : t -> Unknown.t
252252+ (** [unknown t] returns the unknown fields. *)
253253+ end
254254+255255+ (** {2 Output} *)
256256+257257+ module Output : sig
258258+ type t
259259+ (** Typed output for Stop hooks *)
260260+261261+ val jsont : t Jsont.t
262262+ (** [jsont] is the Jsont codec for Stop output. *)
263263+264264+ val continue : unit -> t
265265+ (** [continue ()] creates a continue response. *)
266266+267267+ val block : ?reason:string -> unit -> t
268268+ (** [block ?reason ()] creates a block response.
269269+ @param reason Optional explanation for blocking *)
270270+ end
271271+end
272272+273273+(** SubagentStop hook - fires when a subagent stops *)
274274+module SubagentStop : sig
275275+ (** {2 Input} *)
276276+277277+ module Input : sig
278278+ type t = Stop.Input.t
279279+ (** Same structure as Stop.Input *)
280280+281281+ val jsont : t Jsont.t
282282+ val session_id : t -> string
283283+ val transcript_path : t -> string
284284+ val stop_hook_active : t -> bool
285285+ val unknown : t -> Unknown.t
286286+ end
287287+288288+ (** {2 Output} *)
289289+290290+ module Output : sig
291291+ type t = Stop.Output.t
292292+ (** Same structure as Stop.Output *)
293293+294294+ val jsont : t Jsont.t
295295+ val continue : unit -> t
296296+ val block : ?reason:string -> unit -> t
297297+ end
298298+end
299299+300300+(** PreCompact hook - fires before message compaction *)
301301+module PreCompact : sig
302302+ (** {2 Input} *)
303303+304304+ module Input : sig
305305+ type t
306306+ (** Typed input for PreCompact hooks *)
307307+308308+ val jsont : t Jsont.t
309309+ (** [jsont] is the Jsont codec for PreCompact input. *)
310310+311311+ val session_id : t -> string
312312+ (** [session_id t] returns the session ID. *)
313313+314314+ val transcript_path : t -> string
315315+ (** [transcript_path t] returns the transcript file path. *)
316316+317317+ val unknown : t -> Unknown.t
318318+ (** [unknown t] returns the unknown fields. *)
319319+ end
320320+321321+ (** {2 Output} *)
322322+323323+ module Output : sig
324324+ type t = unit
325325+ (** PreCompact has no specific output *)
326326+327327+ val jsont : t Jsont.t
328328+ (** [jsont] is the Jsont codec for PreCompact output (unit codec). *)
329329+330330+ val continue : unit -> t
331331+ (** [continue ()] returns unit. *)
332332+ end
333333+end
334334+335335+(** {1 Generic Hook Result} *)
336336+337337+type result = {
338338+ decision : decision option;
339339+ system_message : string option;
340340+ hook_specific_output : Jsont.json option;
341341+ unknown : Unknown.t;
342342+}
343343+(** Generic result structure for hooks *)
344344+345345+val result_jsont : result Jsont.t
346346+(** [result_jsont] is the Jsont codec for hook results. *)
347347+348348+val continue :
349349+ ?system_message:string -> ?hook_specific_output:Jsont.json -> unit -> result
350350+(** [continue ?system_message ?hook_specific_output ()] creates a continue
351351+ result.
352352+ @param system_message Optional message to add to system context
353353+ @param hook_specific_output Optional hook-specific output data *)
354354+355355+val block :
356356+ ?system_message:string -> ?hook_specific_output:Jsont.json -> unit -> result
357357+(** [block ?system_message ?hook_specific_output ()] creates a block result.
358358+ @param system_message Optional message to add to system context
359359+ @param hook_specific_output Optional hook-specific output data *)
+67
proto/incoming.ml
···11+(** Incoming messages from Claude CLI.
22+33+ This uses the Control module's request_envelope_jsont and
44+ response_envelope_jsont for control messages, and Message.jsont for
55+ conversation messages. The top-level discriminator is the "type" field. *)
66+77+type t =
88+ | Message of Message.t
99+ | Control_response of Control.response_envelope
1010+ | Control_request of Control.request_envelope
1111+1212+let jsont : t Jsont.t =
1313+ (* Message types use "user", "assistant", "system", "result" as type values.
1414+ Control uses "control_request" and "control_response".
1515+1616+ We use case_mem for all types. Note: we use the inner message codecs
1717+ (User.incoming_jsont, etc.) rather than Message.jsont to avoid nesting
1818+ case_mem on the same "type" field. *)
1919+ let case_control_request =
2020+ Jsont.Object.Case.map "control_request" Control.request_envelope_jsont
2121+ ~dec:(fun v -> Control_request v)
2222+ in
2323+ let case_control_response =
2424+ Jsont.Object.Case.map "control_response" Control.response_envelope_jsont
2525+ ~dec:(fun v -> Control_response v)
2626+ in
2727+ let case_user =
2828+ Jsont.Object.Case.map "user" Message.User.incoming_jsont ~dec:(fun v ->
2929+ Message (Message.User v))
3030+ in
3131+ let case_assistant =
3232+ Jsont.Object.Case.map "assistant" Message.Assistant.incoming_jsont
3333+ ~dec:(fun v -> Message (Message.Assistant v))
3434+ in
3535+ let case_system =
3636+ Jsont.Object.Case.map "system" Message.System.jsont ~dec:(fun v ->
3737+ Message (Message.System v))
3838+ in
3939+ let case_result =
4040+ Jsont.Object.Case.map "result" Message.Result.jsont ~dec:(fun v ->
4141+ Message (Message.Result v))
4242+ in
4343+ let enc_case = function
4444+ | Control_request v -> Jsont.Object.Case.value case_control_request v
4545+ | Control_response v -> Jsont.Object.Case.value case_control_response v
4646+ | Message msg -> (
4747+ match msg with
4848+ | Message.User u -> Jsont.Object.Case.value case_user u
4949+ | Message.Assistant a -> Jsont.Object.Case.value case_assistant a
5050+ | Message.System s -> Jsont.Object.Case.value case_system s
5151+ | Message.Result r -> Jsont.Object.Case.value case_result r)
5252+ in
5353+ let cases =
5454+ Jsont.Object.Case.
5555+ [
5656+ make case_control_request;
5757+ make case_control_response;
5858+ make case_user;
5959+ make case_assistant;
6060+ make case_system;
6161+ make case_result;
6262+ ]
6363+ in
6464+ Jsont.Object.map ~kind:"Incoming" Fun.id
6565+ |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
6666+ ~tag_to_string:Fun.id ~tag_compare:String.compare
6767+ |> Jsont.Object.finish
+21
proto/incoming.mli
···11+(** Incoming messages from the Claude CLI.
22+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.
55+66+ The codec uses the "type" field to discriminate between message types:
77+ - "user", "assistant", "system", "result" -> Message variant
88+ - "control_response" -> Control_response variant
99+ - "control_request" -> Control_request variant
1010+1111+ This provides a clean, type-safe way to decode incoming messages in a single
1212+ operation. *)
1313+1414+type t =
1515+ | Message of Message.t
1616+ | Control_response of Control.response_envelope
1717+ | Control_request of Control.request_envelope
1818+1919+val jsont : t Jsont.t
2020+(** Codec for incoming messages. Uses the "type" field to discriminate. Use
2121+ [Jsont.pp_value jsont ()] for pretty-printing. *)
+368
proto/message.ml
···11+module User = struct
22+ type content = String of string | Blocks of Content_block.t list
33+ type t = { content : content; unknown : Unknown.t }
44+55+ let create_string s = { content = String s; unknown = Unknown.empty }
66+77+ let create_blocks blocks =
88+ { content = Blocks blocks; unknown = Unknown.empty }
99+1010+ let create_with_tool_result ~tool_use_id ~content ?is_error () =
1111+ let tool_result =
1212+ Content_block.tool_result ~tool_use_id ~content ?is_error ()
1313+ in
1414+ { content = Blocks [ tool_result ]; unknown = Unknown.empty }
1515+1616+ let make content unknown = { content; unknown }
1717+ let content t = t.content
1818+ let unknown t = t.unknown
1919+2020+ (* Decode content from json value *)
2121+ let decode_content json =
2222+ match json with
2323+ | Jsont.String (s, _) -> String s
2424+ | Jsont.Array (items, _) ->
2525+ let blocks =
2626+ List.map
2727+ (fun j ->
2828+ match Jsont.Json.decode Content_block.jsont j with
2929+ | Ok v -> v
3030+ | Error e -> invalid_arg ("Invalid content block: " ^ e))
3131+ items
3232+ in
3333+ Blocks blocks
3434+ | _ -> failwith "Content must be string or array"
3535+3636+ (* Encode content to json value *)
3737+ let encode_content = function
3838+ | String s -> Jsont.String (s, Jsont.Meta.none)
3939+ | Blocks blocks ->
4040+ let jsons =
4141+ List.map
4242+ (fun b ->
4343+ match Jsont.Json.encode Content_block.jsont b with
4444+ | Ok json -> json
4545+ | Error e -> invalid_arg ("encode_content: " ^ e))
4646+ blocks
4747+ in
4848+ Jsont.Array (jsons, Jsont.Meta.none)
4949+5050+ let jsont : t Jsont.t =
5151+ Jsont.Object.map ~kind:"User" (fun json_content unknown ->
5252+ let content = decode_content json_content in
5353+ make content unknown)
5454+ |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
5555+ encode_content (content t))
5656+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
5757+ |> Jsont.Object.finish
5858+5959+ (* Jsont codec for parsing incoming user messages from CLI *)
6060+ let incoming_jsont : t Jsont.t =
6161+ let message_jsont =
6262+ Jsont.Object.map ~kind:"UserMessage" (fun json_content ->
6363+ let content = decode_content json_content in
6464+ { content; unknown = Unknown.empty })
6565+ |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
6666+ encode_content (content t))
6767+ |> Jsont.Object.finish
6868+ in
6969+ Jsont.Object.map ~kind:"UserEnvelope" Fun.id
7070+ |> Jsont.Object.mem "message" message_jsont ~enc:Fun.id
7171+ |> Jsont.Object.finish
7272+7373+ (* Jsont codec for outgoing user messages - wraps in message envelope *)
7474+ let outgoing_jsont : t Jsont.t =
7575+ (* The inner message object with role and content *)
7676+ let message_jsont =
7777+ Jsont.Object.map ~kind:"UserOutgoingMessage" (fun _role json_content ->
7878+ let content = decode_content json_content in
7979+ { content; unknown = Unknown.empty })
8080+ |> Jsont.Object.mem "role" Jsont.string ~enc:(fun _ -> "user")
8181+ |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
8282+ encode_content (content t))
8383+ |> Jsont.Object.finish
8484+ in
8585+ Jsont.Object.map ~kind:"UserOutgoingEnvelope" Fun.id
8686+ |> Jsont.Object.mem "message" message_jsont ~enc:Fun.id
8787+ |> Jsont.Object.finish
8888+end
8989+9090+module Assistant = struct
9191+ type error =
9292+ [ `Authentication_failed
9393+ | `Billing_error
9494+ | `Rate_limit
9595+ | `Invalid_request
9696+ | `Server_error
9797+ | `Unknown ]
9898+9999+ let error_jsont : error Jsont.t =
100100+ Jsont.enum
101101+ [
102102+ ("authentication_failed", `Authentication_failed);
103103+ ("billing_error", `Billing_error);
104104+ ("rate_limit", `Rate_limit);
105105+ ("invalid_request", `Invalid_request);
106106+ ("server_error", `Server_error);
107107+ ("unknown", `Unknown);
108108+ ]
109109+110110+ type t = {
111111+ content : Content_block.t list;
112112+ model : string;
113113+ error : error option;
114114+ unknown : Unknown.t;
115115+ }
116116+117117+ let create ~content ~model ?error () =
118118+ { content; model; error; unknown = Unknown.empty }
119119+120120+ let make content model error unknown = { content; model; error; unknown }
121121+ let content t = t.content
122122+ let model t = t.model
123123+ let error t = t.error
124124+ let unknown t = t.unknown
125125+126126+ let jsont : t Jsont.t =
127127+ Jsont.Object.map ~kind:"Assistant" make
128128+ |> Jsont.Object.mem "content" (Jsont.list Content_block.jsont) ~enc:content
129129+ |> Jsont.Object.mem "model" Jsont.string ~enc:model
130130+ |> Jsont.Object.opt_mem "error" error_jsont ~enc:error
131131+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
132132+ |> Jsont.Object.finish
133133+134134+ (* Jsont codec for parsing incoming assistant messages from CLI *)
135135+ let incoming_jsont : t Jsont.t =
136136+ Jsont.Object.map ~kind:"AssistantEnvelope" Fun.id
137137+ |> Jsont.Object.mem "message" jsont ~enc:Fun.id
138138+ |> Jsont.Object.finish
139139+end
140140+141141+module System = struct
142142+ (** System messages as a discriminated union on "subtype" field *)
143143+144144+ type init = {
145145+ session_id : string option;
146146+ model : string option;
147147+ cwd : string option;
148148+ unknown : Unknown.t;
149149+ }
150150+151151+ type error = { error : string; unknown : Unknown.t }
152152+ type t = Init of init | Error of error
153153+154154+ (* Accessors *)
155155+ let session_id = function Init i -> i.session_id | _ -> None
156156+ let model = function Init i -> i.model | _ -> None
157157+ let cwd = function Init i -> i.cwd | _ -> None
158158+ let error_msg = function Error e -> Some e.error | _ -> None
159159+ let unknown = function Init i -> i.unknown | Error e -> e.unknown
160160+161161+ (* Constructors *)
162162+ let init ?session_id ?model ?cwd () =
163163+ Init { session_id; model; cwd; unknown = Unknown.empty }
164164+165165+ let error ~error = Error { error; unknown = Unknown.empty }
166166+167167+ (* Individual record codecs *)
168168+ let init_jsont : init Jsont.t =
169169+ let make session_id model cwd unknown : init =
170170+ { session_id; model; cwd; unknown }
171171+ in
172172+ Jsont.Object.map ~kind:"SystemInit" make
173173+ |> Jsont.Object.opt_mem "session_id" Jsont.string ~enc:(fun (r : init) ->
174174+ r.session_id)
175175+ |> Jsont.Object.opt_mem "model" Jsont.string ~enc:(fun (r : init) ->
176176+ r.model)
177177+ |> Jsont.Object.opt_mem "cwd" Jsont.string ~enc:(fun (r : init) -> r.cwd)
178178+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : init) ->
179179+ r.unknown)
180180+ |> Jsont.Object.finish
181181+182182+ let error_jsont : error Jsont.t =
183183+ let make err unknown : error = { error = err; unknown } in
184184+ Jsont.Object.map ~kind:"SystemError" make
185185+ |> Jsont.Object.mem "error" Jsont.string ~enc:(fun (r : error) -> r.error)
186186+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : error) ->
187187+ r.unknown)
188188+ |> Jsont.Object.finish
189189+190190+ (* Main codec using case_mem for "subtype" discriminator *)
191191+ let jsont : t Jsont.t =
192192+ let case_init =
193193+ Jsont.Object.Case.map "init" init_jsont ~dec:(fun v -> Init v)
194194+ in
195195+ let case_error =
196196+ Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v)
197197+ in
198198+ let enc_case = function
199199+ | Init v -> Jsont.Object.Case.value case_init v
200200+ | Error v -> Jsont.Object.Case.value case_error v
201201+ in
202202+ let cases = Jsont.Object.Case.[ make case_init; make case_error ] in
203203+ Jsont.Object.map ~kind:"System" Fun.id
204204+ |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
205205+ ~tag_to_string:Fun.id ~tag_compare:String.compare
206206+ |> Jsont.Object.finish
207207+end
208208+209209+module Result = struct
210210+ module Usage = struct
211211+ type t = {
212212+ input_tokens : int option;
213213+ output_tokens : int option;
214214+ total_tokens : int option;
215215+ cache_creation_input_tokens : int option;
216216+ cache_read_input_tokens : int option;
217217+ unknown : Unknown.t;
218218+ }
219219+220220+ let make input_tokens output_tokens total_tokens cache_creation_input_tokens
221221+ cache_read_input_tokens unknown =
222222+ {
223223+ input_tokens;
224224+ output_tokens;
225225+ total_tokens;
226226+ cache_creation_input_tokens;
227227+ cache_read_input_tokens;
228228+ unknown;
229229+ }
230230+231231+ let create ?input_tokens ?output_tokens ?total_tokens
232232+ ?cache_creation_input_tokens ?cache_read_input_tokens () =
233233+ {
234234+ input_tokens;
235235+ output_tokens;
236236+ total_tokens;
237237+ cache_creation_input_tokens;
238238+ cache_read_input_tokens;
239239+ unknown = Unknown.empty;
240240+ }
241241+242242+ let input_tokens t = t.input_tokens
243243+ let output_tokens t = t.output_tokens
244244+ let total_tokens t = t.total_tokens
245245+ let cache_creation_input_tokens t = t.cache_creation_input_tokens
246246+ let cache_read_input_tokens t = t.cache_read_input_tokens
247247+ let unknown t = t.unknown
248248+249249+ let jsont : t Jsont.t =
250250+ Jsont.Object.map ~kind:"Usage" make
251251+ |> Jsont.Object.opt_mem "input_tokens" Jsont.int ~enc:input_tokens
252252+ |> Jsont.Object.opt_mem "output_tokens" Jsont.int ~enc:output_tokens
253253+ |> Jsont.Object.opt_mem "total_tokens" Jsont.int ~enc:total_tokens
254254+ |> Jsont.Object.opt_mem "cache_creation_input_tokens" Jsont.int
255255+ ~enc:cache_creation_input_tokens
256256+ |> Jsont.Object.opt_mem "cache_read_input_tokens" Jsont.int
257257+ ~enc:cache_read_input_tokens
258258+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
259259+ |> Jsont.Object.finish
260260+ end
261261+262262+ type t = {
263263+ subtype : string;
264264+ duration_ms : int;
265265+ duration_api_ms : int;
266266+ is_error : bool;
267267+ num_turns : int;
268268+ session_id : string;
269269+ total_cost_usd : float option;
270270+ usage : Usage.t option;
271271+ result : string option;
272272+ structured_output : Jsont.json option;
273273+ unknown : Unknown.t;
274274+ }
275275+276276+ let create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
277277+ ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
278278+ {
279279+ subtype;
280280+ duration_ms;
281281+ duration_api_ms;
282282+ is_error;
283283+ num_turns;
284284+ session_id;
285285+ total_cost_usd;
286286+ usage;
287287+ result;
288288+ structured_output;
289289+ unknown = Unknown.empty;
290290+ }
291291+292292+ let make subtype duration_ms duration_api_ms is_error num_turns session_id
293293+ total_cost_usd usage result structured_output unknown =
294294+ {
295295+ subtype;
296296+ duration_ms;
297297+ duration_api_ms;
298298+ is_error;
299299+ num_turns;
300300+ session_id;
301301+ total_cost_usd;
302302+ usage;
303303+ result;
304304+ structured_output;
305305+ unknown;
306306+ }
307307+308308+ let subtype t = t.subtype
309309+ let duration_ms t = t.duration_ms
310310+ let duration_api_ms t = t.duration_api_ms
311311+ let is_error t = t.is_error
312312+ let num_turns t = t.num_turns
313313+ let session_id t = t.session_id
314314+ let total_cost_usd t = t.total_cost_usd
315315+ let usage t = t.usage
316316+ let result t = t.result
317317+ let structured_output t = t.structured_output
318318+ let unknown t = t.unknown
319319+320320+ let jsont : t Jsont.t =
321321+ Jsont.Object.map ~kind:"Result" make
322322+ |> Jsont.Object.mem "subtype" Jsont.string ~enc:subtype
323323+ |> Jsont.Object.mem "duration_ms" Jsont.int ~enc:duration_ms
324324+ |> Jsont.Object.mem "duration_api_ms" Jsont.int ~enc:duration_api_ms
325325+ |> Jsont.Object.mem "is_error" Jsont.bool ~enc:is_error
326326+ |> Jsont.Object.mem "num_turns" Jsont.int ~enc:num_turns
327327+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
328328+ |> Jsont.Object.opt_mem "total_cost_usd" Jsont.number ~enc:total_cost_usd
329329+ |> Jsont.Object.opt_mem "usage" Usage.jsont ~enc:usage
330330+ |> Jsont.Object.opt_mem "result" Jsont.string ~enc:result
331331+ |> Jsont.Object.opt_mem "structured_output" Jsont.json
332332+ ~enc:structured_output
333333+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
334334+ |> Jsont.Object.finish
335335+end
336336+337337+type t =
338338+ | User of User.t
339339+ | Assistant of Assistant.t
340340+ | System of System.t
341341+ | Result of Result.t
342342+343343+(* Jsont codec for the main Message variant type.
344344+ Uses case_mem for discriminated union based on "type" field. *)
345345+let jsont : t Jsont.t =
346346+ let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
347347+ let case_user = case_map "user" User.incoming_jsont (fun v -> User v) in
348348+ let case_assistant =
349349+ case_map "assistant" Assistant.incoming_jsont (fun v -> Assistant v)
350350+ in
351351+ let case_system = case_map "system" System.jsont (fun v -> System v) in
352352+ let case_result = case_map "result" Result.jsont (fun v -> Result v) in
353353+ let enc_case = function
354354+ | User v -> Jsont.Object.Case.value case_user v
355355+ | Assistant v -> Jsont.Object.Case.value case_assistant v
356356+ | System v -> Jsont.Object.Case.value case_system v
357357+ | Result v -> Jsont.Object.Case.value case_result v
358358+ in
359359+ let cases =
360360+ Jsont.Object.Case.
361361+ [
362362+ make case_user; make case_assistant; make case_system; make case_result;
363363+ ]
364364+ in
365365+ Jsont.Object.map ~kind:"Message" Fun.id
366366+ |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
367367+ ~tag_to_string:Fun.id ~tag_compare:String.compare
368368+ |> Jsont.Object.finish
+352
proto/message.ml.bak
···11+module User = struct
22+ type content = String of string | Blocks of Content_block.t list
33+ type t = { content : content; unknown : Unknown.t }
44+55+ let create_string s = { content = String s; unknown = Unknown.empty }
66+77+ let create_blocks blocks =
88+ { content = Blocks blocks; unknown = Unknown.empty }
99+1010+ let create_with_tool_result ~tool_use_id ~content ?is_error () =
1111+ let tool_result =
1212+ Content_block.tool_result ~tool_use_id ~content ?is_error ()
1313+ in
1414+ { content = Blocks [ tool_result ]; unknown = Unknown.empty }
1515+1616+ let make content unknown = { content; unknown }
1717+ let content t = t.content
1818+ let unknown t = t.unknown
1919+2020+ (* Decode content from json value *)
2121+ let decode_content json =
2222+ match json with
2323+ | Jsont.String (s, _) -> String s
2424+ | Jsont.Array (items, _) ->
2525+ let blocks =
2626+ List.map
2727+ (fun j ->
2828+ match Jsont.Json.decode Content_block.jsont j with
2929+ | Ok v -> v
3030+ | Error e -> invalid_arg ("Invalid content block: " ^ e))
3131+ items
3232+ in
3333+ Blocks blocks
3434+ | _ -> failwith "Content must be string or array"
3535+3636+ (* Encode content to json value *)
3737+ let encode_content = function
3838+ | String s -> Jsont.String (s, Jsont.Meta.none)
3939+ | Blocks blocks ->
4040+ let jsons =
4141+ List.map
4242+ (fun b ->
4343+ match Jsont.Json.encode Content_block.jsont b with
4444+ | Ok json -> json
4545+ | Error e -> invalid_arg ("encode_content: " ^ e))
4646+ blocks
4747+ in
4848+ Jsont.Array (jsons, Jsont.Meta.none)
4949+5050+ let jsont : t Jsont.t =
5151+ Jsont.Object.map ~kind:"User" (fun json_content unknown ->
5252+ let content = decode_content json_content in
5353+ make content unknown)
5454+ |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
5555+ encode_content (content t))
5656+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
5757+ |> Jsont.Object.finish
5858+5959+ (* Jsont codec for parsing incoming user messages from CLI *)
6060+ let incoming_jsont : t Jsont.t =
6161+ let message_jsont =
6262+ Jsont.Object.map ~kind:"UserMessage" (fun json_content ->
6363+ let content = decode_content json_content in
6464+ { content; unknown = Unknown.empty })
6565+ |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
6666+ encode_content (content t))
6767+ |> Jsont.Object.finish
6868+ in
6969+ Jsont.Object.map ~kind:"UserEnvelope" Fun.id
7070+ |> Jsont.Object.mem "message" message_jsont ~enc:Fun.id
7171+ |> Jsont.Object.finish
7272+end
7373+7474+module Assistant = struct
7575+ type error =
7676+ [ `Authentication_failed
7777+ | `Billing_error
7878+ | `Rate_limit
7979+ | `Invalid_request
8080+ | `Server_error
8181+ | `Unknown ]
8282+8383+ let error_jsont : error Jsont.t =
8484+ Jsont.enum
8585+ [
8686+ ("authentication_failed", `Authentication_failed);
8787+ ("billing_error", `Billing_error);
8888+ ("rate_limit", `Rate_limit);
8989+ ("invalid_request", `Invalid_request);
9090+ ("server_error", `Server_error);
9191+ ("unknown", `Unknown);
9292+ ]
9393+9494+ type t = {
9595+ content : Content_block.t list;
9696+ model : string;
9797+ error : error option;
9898+ unknown : Unknown.t;
9999+ }
100100+101101+ let create ~content ~model ?error () =
102102+ { content; model; error; unknown = Unknown.empty }
103103+104104+ let make content model error unknown = { content; model; error; unknown }
105105+ let content t = t.content
106106+ let model t = t.model
107107+ let error t = t.error
108108+ let unknown t = t.unknown
109109+110110+ let jsont : t Jsont.t =
111111+ Jsont.Object.map ~kind:"Assistant" make
112112+ |> Jsont.Object.mem "content" (Jsont.list Content_block.jsont) ~enc:content
113113+ |> Jsont.Object.mem "model" Jsont.string ~enc:model
114114+ |> Jsont.Object.opt_mem "error" error_jsont ~enc:error
115115+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
116116+ |> Jsont.Object.finish
117117+118118+ (* Jsont codec for parsing incoming assistant messages from CLI *)
119119+ let incoming_jsont : t Jsont.t =
120120+ Jsont.Object.map ~kind:"AssistantEnvelope" Fun.id
121121+ |> Jsont.Object.mem "message" jsont ~enc:Fun.id
122122+ |> Jsont.Object.finish
123123+end
124124+125125+module System = struct
126126+ (** System messages as a discriminated union on "subtype" field *)
127127+128128+ type init = {
129129+ session_id : string option;
130130+ model : string option;
131131+ cwd : string option;
132132+ unknown : Unknown.t;
133133+ }
134134+135135+ type error = { error : string; unknown : Unknown.t }
136136+ type t = Init of init | Error of error
137137+138138+ (* Accessors *)
139139+ let session_id = function Init i -> i.session_id | _ -> None
140140+ let model = function Init i -> i.model | _ -> None
141141+ let cwd = function Init i -> i.cwd | _ -> None
142142+ let error_msg = function Error e -> Some e.error | _ -> None
143143+ let unknown = function Init i -> i.unknown | Error e -> e.unknown
144144+145145+ (* Constructors *)
146146+ let init ?session_id ?model ?cwd () =
147147+ Init { session_id; model; cwd; unknown = Unknown.empty }
148148+149149+ let error ~error = Error { error; unknown = Unknown.empty }
150150+151151+ (* Individual record codecs *)
152152+ let init_jsont : init Jsont.t =
153153+ let make session_id model cwd unknown : init =
154154+ { session_id; model; cwd; unknown }
155155+ in
156156+ Jsont.Object.map ~kind:"SystemInit" make
157157+ |> Jsont.Object.opt_mem "session_id" Jsont.string ~enc:(fun (r : init) ->
158158+ r.session_id)
159159+ |> Jsont.Object.opt_mem "model" Jsont.string ~enc:(fun (r : init) ->
160160+ r.model)
161161+ |> Jsont.Object.opt_mem "cwd" Jsont.string ~enc:(fun (r : init) -> r.cwd)
162162+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : init) ->
163163+ r.unknown)
164164+ |> Jsont.Object.finish
165165+166166+ let error_jsont : error Jsont.t =
167167+ let make err unknown : error = { error = err; unknown } in
168168+ Jsont.Object.map ~kind:"SystemError" make
169169+ |> Jsont.Object.mem "error" Jsont.string ~enc:(fun (r : error) -> r.error)
170170+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : error) ->
171171+ r.unknown)
172172+ |> Jsont.Object.finish
173173+174174+ (* Main codec using case_mem for "subtype" discriminator *)
175175+ let jsont : t Jsont.t =
176176+ let case_init =
177177+ Jsont.Object.Case.map "init" init_jsont ~dec:(fun v -> Init v)
178178+ in
179179+ let case_error =
180180+ Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v)
181181+ in
182182+ let enc_case = function
183183+ | Init v -> Jsont.Object.Case.value case_init v
184184+ | Error v -> Jsont.Object.Case.value case_error v
185185+ in
186186+ let cases = Jsont.Object.Case.[ make case_init; make case_error ] in
187187+ Jsont.Object.map ~kind:"System" Fun.id
188188+ |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
189189+ ~tag_to_string:Fun.id ~tag_compare:String.compare
190190+ |> Jsont.Object.finish
191191+end
192192+193193+module Result = struct
194194+ module Usage = struct
195195+ type t = {
196196+ input_tokens : int option;
197197+ output_tokens : int option;
198198+ total_tokens : int option;
199199+ cache_creation_input_tokens : int option;
200200+ cache_read_input_tokens : int option;
201201+ unknown : Unknown.t;
202202+ }
203203+204204+ let make input_tokens output_tokens total_tokens cache_creation_input_tokens
205205+ cache_read_input_tokens unknown =
206206+ {
207207+ input_tokens;
208208+ output_tokens;
209209+ total_tokens;
210210+ cache_creation_input_tokens;
211211+ cache_read_input_tokens;
212212+ unknown;
213213+ }
214214+215215+ let create ?input_tokens ?output_tokens ?total_tokens
216216+ ?cache_creation_input_tokens ?cache_read_input_tokens () =
217217+ {
218218+ input_tokens;
219219+ output_tokens;
220220+ total_tokens;
221221+ cache_creation_input_tokens;
222222+ cache_read_input_tokens;
223223+ unknown = Unknown.empty;
224224+ }
225225+226226+ let input_tokens t = t.input_tokens
227227+ let output_tokens t = t.output_tokens
228228+ let total_tokens t = t.total_tokens
229229+ let cache_creation_input_tokens t = t.cache_creation_input_tokens
230230+ let cache_read_input_tokens t = t.cache_read_input_tokens
231231+ let unknown t = t.unknown
232232+233233+ let jsont : t Jsont.t =
234234+ Jsont.Object.map ~kind:"Usage" make
235235+ |> Jsont.Object.opt_mem "input_tokens" Jsont.int ~enc:input_tokens
236236+ |> Jsont.Object.opt_mem "output_tokens" Jsont.int ~enc:output_tokens
237237+ |> Jsont.Object.opt_mem "total_tokens" Jsont.int ~enc:total_tokens
238238+ |> Jsont.Object.opt_mem "cache_creation_input_tokens" Jsont.int
239239+ ~enc:cache_creation_input_tokens
240240+ |> Jsont.Object.opt_mem "cache_read_input_tokens" Jsont.int
241241+ ~enc:cache_read_input_tokens
242242+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
243243+ |> Jsont.Object.finish
244244+ end
245245+246246+ type t = {
247247+ subtype : string;
248248+ duration_ms : int;
249249+ duration_api_ms : int;
250250+ is_error : bool;
251251+ num_turns : int;
252252+ session_id : string;
253253+ total_cost_usd : float option;
254254+ usage : Usage.t option;
255255+ result : string option;
256256+ structured_output : Jsont.json option;
257257+ unknown : Unknown.t;
258258+ }
259259+260260+ let create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
261261+ ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
262262+ {
263263+ subtype;
264264+ duration_ms;
265265+ duration_api_ms;
266266+ is_error;
267267+ num_turns;
268268+ session_id;
269269+ total_cost_usd;
270270+ usage;
271271+ result;
272272+ structured_output;
273273+ unknown = Unknown.empty;
274274+ }
275275+276276+ let make subtype duration_ms duration_api_ms is_error num_turns session_id
277277+ total_cost_usd usage result structured_output unknown =
278278+ {
279279+ subtype;
280280+ duration_ms;
281281+ duration_api_ms;
282282+ is_error;
283283+ num_turns;
284284+ session_id;
285285+ total_cost_usd;
286286+ usage;
287287+ result;
288288+ structured_output;
289289+ unknown;
290290+ }
291291+292292+ let subtype t = t.subtype
293293+ let duration_ms t = t.duration_ms
294294+ let duration_api_ms t = t.duration_api_ms
295295+ let is_error t = t.is_error
296296+ let num_turns t = t.num_turns
297297+ let session_id t = t.session_id
298298+ let total_cost_usd t = t.total_cost_usd
299299+ let usage t = t.usage
300300+ let result t = t.result
301301+ let structured_output t = t.structured_output
302302+ let unknown t = t.unknown
303303+304304+ let jsont : t Jsont.t =
305305+ Jsont.Object.map ~kind:"Result" make
306306+ |> Jsont.Object.mem "subtype" Jsont.string ~enc:subtype
307307+ |> Jsont.Object.mem "duration_ms" Jsont.int ~enc:duration_ms
308308+ |> Jsont.Object.mem "duration_api_ms" Jsont.int ~enc:duration_api_ms
309309+ |> Jsont.Object.mem "is_error" Jsont.bool ~enc:is_error
310310+ |> Jsont.Object.mem "num_turns" Jsont.int ~enc:num_turns
311311+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
312312+ |> Jsont.Object.opt_mem "total_cost_usd" Jsont.number ~enc:total_cost_usd
313313+ |> Jsont.Object.opt_mem "usage" Usage.jsont ~enc:usage
314314+ |> Jsont.Object.opt_mem "result" Jsont.string ~enc:result
315315+ |> Jsont.Object.opt_mem "structured_output" Jsont.json
316316+ ~enc:structured_output
317317+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
318318+ |> Jsont.Object.finish
319319+end
320320+321321+type t =
322322+ | User of User.t
323323+ | Assistant of Assistant.t
324324+ | System of System.t
325325+ | Result of Result.t
326326+327327+(* Jsont codec for the main Message variant type.
328328+ Uses case_mem for discriminated union based on "type" field. *)
329329+let jsont : t Jsont.t =
330330+ let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
331331+ let case_user = case_map "user" User.incoming_jsont (fun v -> User v) in
332332+ let case_assistant =
333333+ case_map "assistant" Assistant.incoming_jsont (fun v -> Assistant v)
334334+ in
335335+ let case_system = case_map "system" System.jsont (fun v -> System v) in
336336+ let case_result = case_map "result" Result.jsont (fun v -> Result v) in
337337+ let enc_case = function
338338+ | User v -> Jsont.Object.Case.value case_user v
339339+ | Assistant v -> Jsont.Object.Case.value case_assistant v
340340+ | System v -> Jsont.Object.Case.value case_system v
341341+ | Result v -> Jsont.Object.Case.value case_result v
342342+ in
343343+ let cases =
344344+ Jsont.Object.Case.
345345+ [
346346+ make case_user; make case_assistant; make case_system; make case_result;
347347+ ]
348348+ in
349349+ Jsont.Object.map ~kind:"Message" Fun.id
350350+ |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
351351+ ~tag_to_string:Fun.id ~tag_compare:String.compare
352352+ |> Jsont.Object.finish
+270
proto/message.mli
···11+(** Messages exchanged with Claude wire format.
22+33+ This module defines the wire format types for messages that can be sent to
44+ and received from Claude, including user input, assistant responses, system
55+ messages, and result metadata. *)
66+77+(** {1 User Messages} *)
88+99+module User : sig
1010+ (** Messages sent by the user. *)
1111+1212+ (** The content of a user message. *)
1313+ type content =
1414+ | String of string (** Simple text message *)
1515+ | Blocks of Content_block.t list
1616+ (** Complex message with multiple content blocks *)
1717+1818+ type t
1919+ (** The type of user messages. *)
2020+2121+ val jsont : t Jsont.t
2222+ (** [jsont] is the Jsont codec for user messages. *)
2323+2424+ val incoming_jsont : t Jsont.t
2525+ (** [incoming_jsont] is the codec for parsing incoming user messages from CLI.
2626+ This parses the envelope format with "message" wrapper. *)
2727+2828+ val outgoing_jsont : t Jsont.t
2929+ (** [outgoing_jsont] is the codec for encoding outgoing user messages to CLI.
3030+ This produces the envelope format with "message" wrapper containing
3131+ "role" and "content" fields. *)
3232+3333+ val create_string : string -> t
3434+ (** [create_string s] creates a user message with simple text content. *)
3535+3636+ val create_blocks : Content_block.t list -> t
3737+ (** [create_blocks blocks] creates a user message with content blocks. *)
3838+3939+ val create_with_tool_result :
4040+ tool_use_id:string -> content:string -> ?is_error:bool -> unit -> t
4141+ (** [create_with_tool_result ~tool_use_id ~content ?is_error ()] creates a
4242+ user message containing a tool result. *)
4343+4444+ val content : t -> content
4545+ (** [content t] returns the content of the user message. *)
4646+4747+ val unknown : t -> Unknown.t
4848+ (** [unknown t] returns the unknown fields preserved from JSON. *)
4949+end
5050+5151+(** {1 Assistant Messages} *)
5252+5353+module Assistant : sig
5454+ (** Messages from Claude assistant. *)
5555+5656+ type error =
5757+ [ `Authentication_failed (** Authentication with Claude API failed *)
5858+ | `Billing_error (** Billing or account issue *)
5959+ | `Rate_limit (** Rate limit exceeded *)
6060+ | `Invalid_request (** Request was invalid *)
6161+ | `Server_error (** Internal server error *)
6262+ | `Unknown (** Unknown error type *) ]
6363+ (** The type of assistant message errors based on Python SDK error types. *)
6464+6565+ type t
6666+ (** The type of assistant messages. *)
6767+6868+ val jsont : t Jsont.t
6969+ (** [jsont] is the Jsont codec for assistant messages. *)
7070+7171+ val incoming_jsont : t Jsont.t
7272+ (** [incoming_jsont] is the codec for parsing incoming assistant messages from
7373+ CLI. This parses the envelope format with "message" wrapper. *)
7474+7575+ val create :
7676+ content:Content_block.t list -> model:string -> ?error:error -> unit -> t
7777+ (** [create ~content ~model ?error ()] creates an assistant message.
7878+ @param content List of content blocks in the response
7979+ @param model The model identifier used for the response
8080+ @param error Optional error that occurred during message generation *)
8181+8282+ val content : t -> Content_block.t list
8383+ (** [content t] returns the content blocks of the assistant message. *)
8484+8585+ val model : t -> string
8686+ (** [model t] returns the model identifier. *)
8787+8888+ val error : t -> error option
8989+ (** [error t] returns the optional error that occurred during message
9090+ generation. *)
9191+9292+ val unknown : t -> Unknown.t
9393+ (** [unknown t] returns the unknown fields preserved from JSON. *)
9494+end
9595+9696+(** {1 System Messages} *)
9797+9898+module System : sig
9999+ (** System control and status messages.
100100+101101+ System messages use a discriminated union on the "subtype" field:
102102+ - "init": Session initialization with session_id, model, cwd
103103+ - "error": Error messages with error string *)
104104+105105+ type init = {
106106+ session_id : string option;
107107+ model : string option;
108108+ cwd : string option;
109109+ unknown : Unknown.t;
110110+ }
111111+ (** Init message fields. *)
112112+113113+ type error = { error : string; unknown : Unknown.t }
114114+ (** Error message fields. *)
115115+116116+ type t = Init of init | Error of error
117117+118118+ val jsont : t Jsont.t
119119+ (** [jsont] is the Jsont codec for system messages. *)
120120+121121+ (** {2 Constructors} *)
122122+123123+ val init : ?session_id:string -> ?model:string -> ?cwd:string -> unit -> t
124124+ (** [init ?session_id ?model ?cwd ()] creates an init message. *)
125125+126126+ val error : error:string -> t
127127+ (** [error ~error] creates an error message. *)
128128+129129+ (** {2 Accessors} *)
130130+131131+ val session_id : t -> string option
132132+ (** [session_id t] returns session_id from Init, None otherwise. *)
133133+134134+ val model : t -> string option
135135+ (** [model t] returns model from Init, None otherwise. *)
136136+137137+ val cwd : t -> string option
138138+ (** [cwd t] returns cwd from Init, None otherwise. *)
139139+140140+ val error_msg : t -> string option
141141+ (** [error_msg t] returns error from Error, None otherwise. *)
142142+143143+ val unknown : t -> Unknown.t
144144+ (** [unknown t] returns the unknown fields. *)
145145+end
146146+147147+(** {1 Result Messages} *)
148148+149149+module Result : sig
150150+ (** Final result messages with metadata about the conversation. *)
151151+152152+ module Usage : sig
153153+ (** Usage statistics for API calls. *)
154154+155155+ type t
156156+ (** Type for usage statistics. *)
157157+158158+ val jsont : t Jsont.t
159159+ (** [jsont] is the Jsont codec for usage statistics. *)
160160+161161+ val create :
162162+ ?input_tokens:int ->
163163+ ?output_tokens:int ->
164164+ ?total_tokens:int ->
165165+ ?cache_creation_input_tokens:int ->
166166+ ?cache_read_input_tokens:int ->
167167+ unit ->
168168+ t
169169+ (** [create ?input_tokens ?output_tokens ?total_tokens
170170+ ?cache_creation_input_tokens ?cache_read_input_tokens ()] creates usage
171171+ statistics. *)
172172+173173+ val input_tokens : t -> int option
174174+ (** [input_tokens t] returns the number of input tokens used. *)
175175+176176+ val output_tokens : t -> int option
177177+ (** [output_tokens t] returns the number of output tokens generated. *)
178178+179179+ val total_tokens : t -> int option
180180+ (** [total_tokens t] returns the total number of tokens. *)
181181+182182+ val cache_creation_input_tokens : t -> int option
183183+ (** [cache_creation_input_tokens t] returns cache creation input tokens. *)
184184+185185+ val cache_read_input_tokens : t -> int option
186186+ (** [cache_read_input_tokens t] returns cache read input tokens. *)
187187+188188+ val unknown : t -> Unknown.t
189189+ (** [unknown t] returns the unknown fields preserved from JSON. *)
190190+ end
191191+192192+ type t
193193+ (** The type of result messages. *)
194194+195195+ val jsont : t Jsont.t
196196+ (** [jsont] is the Jsont codec for result messages. *)
197197+198198+ val create :
199199+ subtype:string ->
200200+ duration_ms:int ->
201201+ duration_api_ms:int ->
202202+ is_error:bool ->
203203+ num_turns:int ->
204204+ session_id:string ->
205205+ ?total_cost_usd:float ->
206206+ ?usage:Usage.t ->
207207+ ?result:string ->
208208+ ?structured_output:Jsont.json ->
209209+ unit ->
210210+ t
211211+ (** [create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
212212+ ~session_id ?total_cost_usd ?usage ?result ?structured_output ()] creates
213213+ a result message.
214214+ @param subtype The subtype of the result
215215+ @param duration_ms Total duration in milliseconds
216216+ @param duration_api_ms API duration in milliseconds
217217+ @param is_error Whether the result represents an error
218218+ @param num_turns Number of conversation turns
219219+ @param session_id Unique session identifier
220220+ @param total_cost_usd Optional total cost in USD
221221+ @param usage Optional usage statistics
222222+ @param result Optional result string
223223+ @param structured_output Optional structured JSON output from Claude *)
224224+225225+ val subtype : t -> string
226226+ (** [subtype t] returns the subtype of the result. *)
227227+228228+ val duration_ms : t -> int
229229+ (** [duration_ms t] returns the total duration in milliseconds. *)
230230+231231+ val duration_api_ms : t -> int
232232+ (** [duration_api_ms t] returns the API duration in milliseconds. *)
233233+234234+ val is_error : t -> bool
235235+ (** [is_error t] returns whether this result represents an error. *)
236236+237237+ val num_turns : t -> int
238238+ (** [num_turns t] returns the number of conversation turns. *)
239239+240240+ val session_id : t -> string
241241+ (** [session_id t] returns the session identifier. *)
242242+243243+ val total_cost_usd : t -> float option
244244+ (** [total_cost_usd t] returns the optional total cost in USD. *)
245245+246246+ val usage : t -> Usage.t option
247247+ (** [usage t] returns the optional usage statistics. *)
248248+249249+ val result : t -> string option
250250+ (** [result t] returns the optional result string. *)
251251+252252+ val structured_output : t -> Jsont.json option
253253+ (** [structured_output t] returns the optional structured JSON output. *)
254254+255255+ val unknown : t -> Unknown.t
256256+ (** [unknown t] returns the unknown fields preserved from JSON. *)
257257+end
258258+259259+(** {1 Message Union Type} *)
260260+261261+type t =
262262+ | User of User.t
263263+ | Assistant of Assistant.t
264264+ | System of System.t
265265+ | Result of Result.t
266266+ (** The type of messages, which can be user, assistant, system, or result.
267267+ *)
268268+269269+val jsont : t Jsont.t
270270+(** [jsont] is the Jsont codec for messages. *)
···11+(** Claude AI model identifiers for protocol encoding.
22+33+ This module provides type-safe model identifiers with JSON encoding/decoding
44+ support via Jsont. Use polymorphic variants for known models with a custom
55+ escape hatch for future or unknown models. *)
66+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 *) ]
1414+(** The type of Claude models. *)
1515+1616+val to_string : t -> string
1717+(** [to_string t] converts a model to its string representation.
1818+1919+ Examples:
2020+ - [`Sonnet_4_5] becomes "claude-sonnet-4-5"
2121+ - [`Opus_4] becomes "claude-opus-4"
2222+ - [`Custom "my-model"] becomes "my-model" *)
2323+2424+val of_string : string -> t
2525+(** [of_string s] parses a model string into a typed model.
2626+2727+ Known model strings are converted to their typed variants. Unknown strings
2828+ become [`Custom s].
2929+3030+ Examples:
3131+ - "claude-sonnet-4-5" becomes [`Sonnet_4_5]
3232+ - "future-model" becomes [`Custom "future-model"] *)
3333+3434+val jsont : t Jsont.t
3535+(** [jsont] is the Jsont codec for model identifiers.
3636+3737+ This codec maps between the typed model representation and JSON strings. It
3838+ uses [of_string] for decoding and [to_string] for encoding. *)
+182
proto/options.ml
···11+(** Wire format for Claude configuration options. *)
22+33+(** Setting sources *)
44+type setting_source = User | Project | Local
55+66+let setting_source_jsont : setting_source Jsont.t =
77+ Jsont.enum [ ("user", User); ("project", Project); ("local", Local) ]
88+99+(** Configuration type *)
1010+type t = {
1111+ allowed_tools : string list;
1212+ disallowed_tools : string list;
1313+ max_thinking_tokens : int option;
1414+ system_prompt : string option;
1515+ append_system_prompt : string option;
1616+ permission_mode : Permissions.Mode.t option;
1717+ model : Model.t option;
1818+ continue_conversation : bool;
1919+ resume : string option;
2020+ max_turns : int option;
2121+ permission_prompt_tool_name : string option;
2222+ settings : string option;
2323+ add_dirs : string list;
2424+ max_budget_usd : float option;
2525+ fallback_model : Model.t option;
2626+ setting_sources : setting_source list option;
2727+ max_buffer_size : int option;
2828+ user : string option;
2929+ output_format : Structured_output.t option;
3030+ unknown : Unknown.t;
3131+}
3232+3333+let empty =
3434+ {
3535+ allowed_tools = [];
3636+ disallowed_tools = [];
3737+ max_thinking_tokens = None;
3838+ system_prompt = None;
3939+ append_system_prompt = None;
4040+ permission_mode = None;
4141+ model = None;
4242+ continue_conversation = false;
4343+ resume = None;
4444+ max_turns = None;
4545+ permission_prompt_tool_name = None;
4646+ settings = None;
4747+ add_dirs = [];
4848+ max_budget_usd = None;
4949+ fallback_model = None;
5050+ setting_sources = None;
5151+ max_buffer_size = None;
5252+ user = None;
5353+ output_format = None;
5454+ unknown = Unknown.empty;
5555+ }
5656+5757+(** Accessor functions *)
5858+let allowed_tools t = t.allowed_tools
5959+let disallowed_tools t = t.disallowed_tools
6060+let max_thinking_tokens t = t.max_thinking_tokens
6161+let system_prompt t = t.system_prompt
6262+let append_system_prompt t = t.append_system_prompt
6363+let permission_mode t = t.permission_mode
6464+let model t = t.model
6565+let continue_conversation t = t.continue_conversation
6666+let resume t = t.resume
6767+let max_turns t = t.max_turns
6868+let permission_prompt_tool_name t = t.permission_prompt_tool_name
6969+let settings t = t.settings
7070+let add_dirs t = t.add_dirs
7171+let max_budget_usd t = t.max_budget_usd
7272+let fallback_model t = t.fallback_model
7373+let setting_sources t = t.setting_sources
7474+let max_buffer_size t = t.max_buffer_size
7575+let user t = t.user
7676+let output_format t = t.output_format
7777+let unknown t = t.unknown
7878+7979+(** Builder functions *)
8080+let with_allowed_tools allowed_tools t = { t with allowed_tools }
8181+let with_disallowed_tools disallowed_tools t = { t with disallowed_tools }
8282+8383+let with_max_thinking_tokens max_thinking_tokens t =
8484+ { t with max_thinking_tokens = Some max_thinking_tokens }
8585+8686+let with_system_prompt system_prompt t =
8787+ { t with system_prompt = Some system_prompt }
8888+8989+let with_append_system_prompt append_system_prompt t =
9090+ { t with append_system_prompt = Some append_system_prompt }
9191+9292+let with_permission_mode permission_mode t =
9393+ { t with permission_mode = Some permission_mode }
9494+9595+let with_model model t = { t with model = Some model }
9696+9797+let with_continue_conversation continue_conversation t =
9898+ { t with continue_conversation }
9999+100100+let with_resume resume t = { t with resume = Some resume }
101101+let with_max_turns max_turns t = { t with max_turns = Some max_turns }
102102+103103+let with_permission_prompt_tool_name permission_prompt_tool_name t =
104104+ { t with permission_prompt_tool_name = Some permission_prompt_tool_name }
105105+106106+let with_settings settings t = { t with settings = Some settings }
107107+let with_add_dirs add_dirs t = { t with add_dirs }
108108+109109+let with_max_budget_usd max_budget_usd t =
110110+ { t with max_budget_usd = Some max_budget_usd }
111111+112112+let with_fallback_model fallback_model t =
113113+ { t with fallback_model = Some fallback_model }
114114+115115+let with_setting_sources setting_sources t =
116116+ { t with setting_sources = Some setting_sources }
117117+118118+let with_max_buffer_size max_buffer_size t =
119119+ { t with max_buffer_size = Some max_buffer_size }
120120+121121+let with_user user t = { t with user = Some user }
122122+123123+let with_output_format output_format t =
124124+ { t with output_format = Some output_format }
125125+126126+(** JSON codec *)
127127+let jsont : t Jsont.t =
128128+ let make allowed_tools disallowed_tools max_thinking_tokens system_prompt
129129+ append_system_prompt permission_mode model continue_conversation resume
130130+ max_turns permission_prompt_tool_name settings add_dirs max_budget_usd
131131+ fallback_model setting_sources max_buffer_size user output_format unknown =
132132+ {
133133+ allowed_tools;
134134+ disallowed_tools;
135135+ max_thinking_tokens;
136136+ system_prompt;
137137+ append_system_prompt;
138138+ permission_mode;
139139+ model;
140140+ continue_conversation;
141141+ resume;
142142+ max_turns;
143143+ permission_prompt_tool_name;
144144+ settings;
145145+ add_dirs;
146146+ max_budget_usd;
147147+ fallback_model;
148148+ setting_sources;
149149+ max_buffer_size;
150150+ user;
151151+ output_format;
152152+ unknown;
153153+ }
154154+ in
155155+ Jsont.Object.(
156156+ map ~kind:"Options" make
157157+ |> mem "allowedTools" (Jsont.list Jsont.string) ~enc:allowed_tools
158158+ ~dec_absent:[]
159159+ |> mem "disallowedTools" (Jsont.list Jsont.string) ~enc:disallowed_tools
160160+ ~dec_absent:[]
161161+ |> opt_mem "maxThinkingTokens" Jsont.int ~enc:max_thinking_tokens
162162+ |> opt_mem "systemPrompt" Jsont.string ~enc:system_prompt
163163+ |> opt_mem "appendSystemPrompt" Jsont.string ~enc:append_system_prompt
164164+ |> opt_mem "permissionMode" Permissions.Mode.jsont ~enc:permission_mode
165165+ |> opt_mem "model" Model.jsont ~enc:model
166166+ |> mem "continueConversation" Jsont.bool ~enc:continue_conversation
167167+ ~dec_absent:false
168168+ |> opt_mem "resume" Jsont.string ~enc:resume
169169+ |> opt_mem "maxTurns" Jsont.int ~enc:max_turns
170170+ |> opt_mem "permissionPromptToolName" Jsont.string
171171+ ~enc:permission_prompt_tool_name
172172+ |> opt_mem "settings" Jsont.string ~enc:settings
173173+ |> mem "addDirs" (Jsont.list Jsont.string) ~enc:add_dirs ~dec_absent:[]
174174+ |> opt_mem "maxBudgetUsd" Jsont.number ~enc:max_budget_usd
175175+ |> opt_mem "fallbackModel" Model.jsont ~enc:fallback_model
176176+ |> opt_mem "settingSources" (Jsont.list setting_source_jsont)
177177+ ~enc:setting_sources
178178+ |> opt_mem "maxBufferSize" Jsont.int ~enc:max_buffer_size
179179+ |> opt_mem "user" Jsont.string ~enc:user
180180+ |> opt_mem "outputFormat" Structured_output.jsont ~enc:output_format
181181+ |> keep_unknown Unknown.mems ~enc:unknown
182182+ |> finish)
+192
proto/options.mli
···11+(** Wire format for Claude configuration options.
22+33+ This module provides the protocol-level wire format encoding/decoding for
44+ configuration options used in JSON configuration files. It handles JSON
55+ serialization and deserialization with proper field name mappings
66+ (camelCase).
77+88+ This is the protocol-level module without Eio types or logging. *)
99+1010+(** {1 Setting Sources} *)
1111+1212+type setting_source =
1313+ | User (** User-level settings *)
1414+ | Project (** Project-level settings *)
1515+ | Local (** Local directory settings *)
1616+(** The type of setting sources, indicating where configuration was loaded
1717+ from. *)
1818+1919+(** {1 Configuration Type} *)
2020+2121+type t
2222+(** The type of configuration options.
2323+2424+ This represents all configurable options for Claude interactions, encoded
2525+ in JSON format. *)
2626+2727+val jsont : t Jsont.t
2828+(** [jsont] is the Jsont codec for configuration options.
2929+3030+ Wire format uses camelCase field names:
3131+ - allowedTools (array of strings)
3232+ - disallowedTools (array of strings)
3333+ - maxThinkingTokens (int)
3434+ - systemPrompt (string)
3535+ - appendSystemPrompt (string)
3636+ - permissionMode (string via Permissions.Mode.jsont)
3737+ - model (string via Model.jsont)
3838+ - continueConversation (bool)
3939+ - resume (string)
4040+ - maxTurns (int)
4141+ - permissionPromptToolName (string)
4242+ - settings (string)
4343+ - addDirs (array of strings)
4444+ - maxBudgetUsd (float)
4545+ - fallbackModel (string via Model.jsont)
4646+ - settingSources (array of "user", "project", "local")
4747+ - maxBufferSize (int)
4848+ - user (string)
4949+ - outputFormat (object via Structured_output.jsont)
5050+5151+ Unknown fields are preserved for forward compatibility. *)
5252+5353+val empty : t
5454+(** [empty] is an empty configuration with all fields set to their default
5555+ values.
5656+5757+ Default values:
5858+ - Lists default to empty
5959+ - [maxThinkingTokens] defaults to 8000
6060+ - [continueConversation] defaults to false
6161+ - All optional fields default to [None] *)
6262+6363+(** {1 Accessor Functions} *)
6464+6565+val allowed_tools : t -> string list
6666+(** [allowed_tools t] returns the list of allowed tool names. Empty list means
6767+ all tools are allowed (unless explicitly disallowed). *)
6868+6969+val disallowed_tools : t -> string list
7070+(** [disallowed_tools t] returns the list of disallowed tool names. *)
7171+7272+val max_thinking_tokens : t -> int option
7373+(** [max_thinking_tokens t] returns the maximum number of tokens Claude can use
7474+ for internal thinking. *)
7575+7676+val system_prompt : t -> string option
7777+(** [system_prompt t] returns the system prompt to use for Claude. *)
7878+7979+val append_system_prompt : t -> string option
8080+(** [append_system_prompt t] returns additional text to append to the system
8181+ prompt. *)
8282+8383+val permission_mode : t -> Permissions.Mode.t option
8484+(** [permission_mode t] returns the permission mode controlling how tool
8585+ invocations are authorized. *)
8686+8787+val model : t -> Model.t option
8888+(** [model t] returns the Claude model to use for interactions. *)
8989+9090+val continue_conversation : t -> bool
9191+(** [continue_conversation t] returns whether to continue from a previous
9292+ conversation. *)
9393+9494+val resume : t -> string option
9595+(** [resume t] returns the session ID to resume from. *)
9696+9797+val max_turns : t -> int option
9898+(** [max_turns t] returns the maximum number of conversation turns to allow. *)
9999+100100+val permission_prompt_tool_name : t -> string option
101101+(** [permission_prompt_tool_name t] returns the tool name to use for permission
102102+ prompts. *)
103103+104104+val settings : t -> string option
105105+(** [settings t] returns the path to the settings file. *)
106106+107107+val add_dirs : t -> string list
108108+(** [add_dirs t] returns additional directories to include in the context. *)
109109+110110+val max_budget_usd : t -> float option
111111+(** [max_budget_usd t] returns the maximum budget in USD for API calls. *)
112112+113113+val fallback_model : t -> Model.t option
114114+(** [fallback_model t] returns the fallback model to use if the primary model
115115+ fails. *)
116116+117117+val setting_sources : t -> setting_source list option
118118+(** [setting_sources t] returns the list of setting sources to load from. *)
119119+120120+val max_buffer_size : t -> int option
121121+(** [max_buffer_size t] returns the maximum buffer size for I/O operations. *)
122122+123123+val user : t -> string option
124124+(** [user t] returns the user identifier for the session. *)
125125+126126+val output_format : t -> Structured_output.t option
127127+(** [output_format t] returns the structured output format configuration. *)
128128+129129+val unknown : t -> Unknown.t
130130+(** [unknown t] returns the unknown fields preserved from JSON parsing. *)
131131+132132+(** {1 Builder Functions} *)
133133+134134+val with_allowed_tools : string list -> t -> t
135135+(** [with_allowed_tools tools t] sets the allowed tools. *)
136136+137137+val with_disallowed_tools : string list -> t -> t
138138+(** [with_disallowed_tools tools t] sets the disallowed tools. *)
139139+140140+val with_max_thinking_tokens : int -> t -> t
141141+(** [with_max_thinking_tokens tokens t] sets the maximum thinking tokens. *)
142142+143143+val with_system_prompt : string -> t -> t
144144+(** [with_system_prompt prompt t] sets the system prompt. *)
145145+146146+val with_append_system_prompt : string -> t -> t
147147+(** [with_append_system_prompt prompt t] sets the text to append to the system
148148+ prompt. *)
149149+150150+val with_permission_mode : Permissions.Mode.t -> t -> t
151151+(** [with_permission_mode mode t] sets the permission mode. *)
152152+153153+val with_model : Model.t -> t -> t
154154+(** [with_model model t] sets the Claude model. *)
155155+156156+val with_continue_conversation : bool -> t -> t
157157+(** [with_continue_conversation continue t] sets whether to continue
158158+ conversation. *)
159159+160160+val with_resume : string -> t -> t
161161+(** [with_resume session_id t] sets the session ID to resume from. *)
162162+163163+val with_max_turns : int -> t -> t
164164+(** [with_max_turns turns t] sets the maximum number of turns. *)
165165+166166+val with_permission_prompt_tool_name : string -> t -> t
167167+(** [with_permission_prompt_tool_name tool t] sets the permission prompt tool
168168+ name. *)
169169+170170+val with_settings : string -> t -> t
171171+(** [with_settings path t] sets the settings file path. *)
172172+173173+val with_add_dirs : string list -> t -> t
174174+(** [with_add_dirs dirs t] sets the additional directories. *)
175175+176176+val with_max_budget_usd : float -> t -> t
177177+(** [with_max_budget_usd budget t] sets the maximum budget. *)
178178+179179+val with_fallback_model : Model.t -> t -> t
180180+(** [with_fallback_model model t] sets the fallback model. *)
181181+182182+val with_setting_sources : setting_source list -> t -> t
183183+(** [with_setting_sources sources t] sets the setting sources. *)
184184+185185+val with_max_buffer_size : int -> t -> t
186186+(** [with_max_buffer_size size t] sets the maximum buffer size. *)
187187+188188+val with_user : string -> t -> t
189189+(** [with_user user t] sets the user identifier. *)
190190+191191+val with_output_format : Structured_output.t -> t -> t
192192+(** [with_output_format format t] sets the structured output format. *)
+77
proto/outgoing.ml
···11+(** Outgoing messages to Claude CLI.
22+33+ This uses the Message.jsont for conversation messages and Control envelope
44+ codecs for control messages. The top-level discriminator is the "type"
55+ field. *)
66+77+type t =
88+ | Message of Message.t
99+ | Control_request of Control.request_envelope
1010+ | Control_response of Control.response_envelope
1111+1212+let jsont : t Jsont.t =
1313+ (* Message types use "user", "assistant", "system", "result" as type values.
1414+ Control uses "control_request" and "control_response".
1515+1616+ We use case_mem for all types. For Message, we use Message.jsont which
1717+ already handles the inner "type" discrimination. *)
1818+ let case_control_request =
1919+ Jsont.Object.Case.map "control_request" Control.request_envelope_jsont
2020+ ~dec:(fun v -> Control_request v)
2121+ in
2222+ let case_control_response =
2323+ Jsont.Object.Case.map "control_response" Control.response_envelope_jsont
2424+ ~dec:(fun v -> Control_response v)
2525+ in
2626+ (* For messages, we need to handle all four message types *)
2727+ let case_user =
2828+ Jsont.Object.Case.map "user" Message.User.outgoing_jsont ~dec:(fun v ->
2929+ Message (Message.User v))
3030+ in
3131+ let case_assistant =
3232+ Jsont.Object.Case.map "assistant" Message.Assistant.jsont ~dec:(fun v ->
3333+ Message (Message.Assistant v))
3434+ in
3535+ let case_system =
3636+ Jsont.Object.Case.map "system" Message.System.jsont ~dec:(fun v ->
3737+ Message (Message.System v))
3838+ in
3939+ let case_result =
4040+ Jsont.Object.Case.map "result" Message.Result.jsont ~dec:(fun v ->
4141+ Message (Message.Result v))
4242+ in
4343+ let enc_case = function
4444+ | Control_request v -> Jsont.Object.Case.value case_control_request v
4545+ | Control_response v -> Jsont.Object.Case.value case_control_response v
4646+ | Message msg -> (
4747+ match msg with
4848+ | Message.User u -> Jsont.Object.Case.value case_user u
4949+ | Message.Assistant a -> Jsont.Object.Case.value case_assistant a
5050+ | Message.System s -> Jsont.Object.Case.value case_system s
5151+ | Message.Result r -> Jsont.Object.Case.value case_result r)
5252+ in
5353+ let cases =
5454+ Jsont.Object.Case.
5555+ [
5656+ make case_control_request;
5757+ make case_control_response;
5858+ make case_user;
5959+ make case_assistant;
6060+ make case_system;
6161+ make case_result;
6262+ ]
6363+ in
6464+ Jsont.Object.map ~kind:"Outgoing" Fun.id
6565+ |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
6666+ ~tag_to_string:Fun.id ~tag_compare:String.compare
6767+ |> Jsont.Object.finish
6868+6969+let to_json t =
7070+ match Jsont.Json.encode jsont t with
7171+ | Ok json -> json
7272+ | Error e -> invalid_arg ("to_json: " ^ e)
7373+7474+let of_json json =
7575+ match Jsont.Json.decode jsont json with
7676+ | Ok v -> v
7777+ | Error e -> invalid_arg ("of_json: " ^ e)
+19
proto/outgoing.mli
···11+(** Outgoing messages to the Claude CLI.
22+33+ This module provides encoding for all message types that can be sent to the
44+ Claude CLI. *)
55+66+type t =
77+ | Message of Message.t
88+ | Control_request of Control.request_envelope
99+ | Control_response of Control.response_envelope
1010+1111+val jsont : t Jsont.t
1212+(** Codec for outgoing messages. *)
1313+1414+val to_json : t -> Jsont.json
1515+(** [to_json t] converts an outgoing message to JSON. *)
1616+1717+val of_json : Jsont.json -> t
1818+(** [of_json json] parses an outgoing message from JSON.
1919+ @raise Invalid_argument if parsing fails. *)
+242
proto/permissions.ml
···11+(** Permission system wire format for Claude tool invocations.
22+33+ This module provides the wire format encoding/decoding for permission types
44+ used in the Claude protocol. It handles JSON serialization and
55+ deserialization with proper field name mappings. *)
66+77+(** Permission modes *)
88+module Mode = struct
99+ type t = Default | Accept_edits | Plan | Bypass_permissions
1010+1111+ let to_string = function
1212+ | Default -> "default"
1313+ | Accept_edits -> "acceptEdits"
1414+ | Plan -> "plan"
1515+ | Bypass_permissions -> "bypassPermissions"
1616+1717+ let of_string = function
1818+ | "default" -> Default
1919+ | "acceptEdits" -> Accept_edits
2020+ | "plan" -> Plan
2121+ | "bypassPermissions" -> Bypass_permissions
2222+ | s ->
2323+ raise
2424+ (Invalid_argument (Printf.sprintf "Mode.of_string: unknown mode %s" s))
2525+2626+ let jsont : t Jsont.t =
2727+ Jsont.enum
2828+ [
2929+ ("default", Default);
3030+ ("acceptEdits", Accept_edits);
3131+ ("plan", Plan);
3232+ ("bypassPermissions", Bypass_permissions);
3333+ ]
3434+end
3535+3636+(** Permission behaviors *)
3737+module Behavior = struct
3838+ type t = Allow | Deny | Ask
3939+4040+ let to_string = function Allow -> "allow" | Deny -> "deny" | Ask -> "ask"
4141+4242+ let of_string = function
4343+ | "allow" -> Allow
4444+ | "deny" -> Deny
4545+ | "ask" -> Ask
4646+ | s ->
4747+ raise
4848+ (Invalid_argument
4949+ (Printf.sprintf "Behavior.of_string: unknown behavior %s" s))
5050+5151+ let jsont : t Jsont.t =
5252+ Jsont.enum [ ("allow", Allow); ("deny", Deny); ("ask", Ask) ]
5353+end
5454+5555+(** Permission rules *)
5656+module Rule = struct
5757+ type t = {
5858+ tool_name : string;
5959+ rule_content : string option;
6060+ unknown : Unknown.t;
6161+ }
6262+6363+ let create ~tool_name ?rule_content ?(unknown = Unknown.empty) () =
6464+ { tool_name; rule_content; unknown }
6565+6666+ let tool_name t = t.tool_name
6767+ let rule_content t = t.rule_content
6868+ let unknown t = t.unknown
6969+7070+ let jsont : t Jsont.t =
7171+ let make tool_name rule_content unknown =
7272+ { tool_name; rule_content; unknown }
7373+ in
7474+ Jsont.Object.map ~kind:"Rule" make
7575+ |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
7676+ |> Jsont.Object.opt_mem "rule_content" Jsont.string ~enc:rule_content
7777+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
7878+ |> Jsont.Object.finish
7979+end
8080+8181+(** Permission updates *)
8282+module Update = struct
8383+ type destination =
8484+ | User_settings
8585+ | Project_settings
8686+ | Local_settings
8787+ | Session
8888+8989+ let destination_jsont : destination Jsont.t =
9090+ Jsont.enum
9191+ [
9292+ ("userSettings", User_settings);
9393+ ("projectSettings", Project_settings);
9494+ ("localSettings", Local_settings);
9595+ ("session", Session);
9696+ ]
9797+9898+ type update_type =
9999+ | Add_rules
100100+ | Replace_rules
101101+ | Remove_rules
102102+ | Set_mode
103103+ | Add_directories
104104+ | Remove_directories
105105+106106+ let update_type_jsont : update_type Jsont.t =
107107+ Jsont.enum
108108+ [
109109+ ("addRules", Add_rules);
110110+ ("replaceRules", Replace_rules);
111111+ ("removeRules", Remove_rules);
112112+ ("setMode", Set_mode);
113113+ ("addDirectories", Add_directories);
114114+ ("removeDirectories", Remove_directories);
115115+ ]
116116+117117+ type t = {
118118+ update_type : update_type;
119119+ rules : Rule.t list option;
120120+ behavior : Behavior.t option;
121121+ mode : Mode.t option;
122122+ directories : string list option;
123123+ destination : destination option;
124124+ unknown : Unknown.t;
125125+ }
126126+127127+ let create ~update_type ?rules ?behavior ?mode ?directories ?destination
128128+ ?(unknown = Unknown.empty) () =
129129+ { update_type; rules; behavior; mode; directories; destination; unknown }
130130+131131+ let update_type t = t.update_type
132132+ let rules t = t.rules
133133+ let behavior t = t.behavior
134134+ let mode t = t.mode
135135+ let directories t = t.directories
136136+ let destination t = t.destination
137137+ let unknown t = t.unknown
138138+139139+ let jsont : t Jsont.t =
140140+ let make update_type rules behavior mode directories destination unknown =
141141+ { update_type; rules; behavior; mode; directories; destination; unknown }
142142+ in
143143+ Jsont.Object.map ~kind:"Update" make
144144+ |> Jsont.Object.mem "type" update_type_jsont ~enc:update_type
145145+ |> Jsont.Object.opt_mem "rules" (Jsont.list Rule.jsont) ~enc:rules
146146+ |> Jsont.Object.opt_mem "behavior" Behavior.jsont ~enc:behavior
147147+ |> Jsont.Object.opt_mem "mode" Mode.jsont ~enc:mode
148148+ |> Jsont.Object.opt_mem "directories" (Jsont.list Jsont.string)
149149+ ~enc:directories
150150+ |> Jsont.Object.opt_mem "destination" destination_jsont ~enc:destination
151151+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
152152+ |> Jsont.Object.finish
153153+end
154154+155155+(** Permission context for callbacks *)
156156+module Context = struct
157157+ type t = { suggestions : Update.t list; unknown : Unknown.t }
158158+159159+ let create ?(suggestions = []) ?(unknown = Unknown.empty) () =
160160+ { suggestions; unknown }
161161+162162+ let suggestions t = t.suggestions
163163+ let unknown t = t.unknown
164164+165165+ let jsont : t Jsont.t =
166166+ let make suggestions unknown = { suggestions; unknown } in
167167+ Jsont.Object.map ~kind:"Context" make
168168+ |> Jsont.Object.mem "suggestions" (Jsont.list Update.jsont) ~enc:suggestions
169169+ ~dec_absent:[]
170170+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
171171+ |> Jsont.Object.finish
172172+end
173173+174174+(** Permission results *)
175175+module Result = struct
176176+ type t =
177177+ | Allow of {
178178+ updated_input : Jsont.json option;
179179+ updated_permissions : Update.t list option;
180180+ unknown : Unknown.t;
181181+ }
182182+ | Deny of { message : string; interrupt : bool; unknown : Unknown.t }
183183+184184+ let allow ?updated_input ?updated_permissions ?(unknown = Unknown.empty) () =
185185+ Allow { updated_input; updated_permissions; unknown }
186186+187187+ let deny ~message ~interrupt ?(unknown = Unknown.empty) () =
188188+ Deny { message; interrupt; unknown }
189189+190190+ let jsont : t Jsont.t =
191191+ let allow_record =
192192+ let make updated_input updated_permissions unknown =
193193+ Allow { updated_input; updated_permissions; unknown }
194194+ in
195195+ Jsont.Object.map ~kind:"AllowRecord" make
196196+ |> Jsont.Object.opt_mem "updated_input" Jsont.json ~enc:(function
197197+ | Allow { updated_input; _ } -> updated_input
198198+ | _ -> None)
199199+ |> Jsont.Object.opt_mem "updated_permissions" (Jsont.list Update.jsont)
200200+ ~enc:(function
201201+ | Allow { updated_permissions; _ } -> updated_permissions
202202+ | _ -> None)
203203+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(function
204204+ | Allow { unknown; _ } -> unknown
205205+ | _ -> Unknown.empty)
206206+ |> Jsont.Object.finish
207207+ in
208208+ let deny_record =
209209+ let make message interrupt unknown =
210210+ Deny { message; interrupt; unknown }
211211+ in
212212+ Jsont.Object.map ~kind:"DenyRecord" make
213213+ |> Jsont.Object.mem "message" Jsont.string ~enc:(function
214214+ | Deny { message; _ } -> message
215215+ | _ -> "")
216216+ |> Jsont.Object.mem "interrupt" Jsont.bool ~enc:(function
217217+ | Deny { interrupt; _ } -> interrupt
218218+ | _ -> false)
219219+ |> Jsont.Object.keep_unknown Unknown.mems ~enc:(function
220220+ | Deny { unknown; _ } -> unknown
221221+ | _ -> Unknown.empty)
222222+ |> Jsont.Object.finish
223223+ in
224224+ let case_allow =
225225+ Jsont.Object.Case.map "allow" allow_record ~dec:(fun v -> v)
226226+ in
227227+ let case_deny =
228228+ Jsont.Object.Case.map "deny" deny_record ~dec:(fun v -> v)
229229+ in
230230+231231+ let enc_case = function
232232+ | Allow _ as v -> Jsont.Object.Case.value case_allow v
233233+ | Deny _ as v -> Jsont.Object.Case.value case_deny v
234234+ in
235235+236236+ let cases = Jsont.Object.Case.[ make case_allow; make case_deny ] in
237237+238238+ Jsont.Object.map ~kind:"Result" Fun.id
239239+ |> Jsont.Object.case_mem "behavior" Jsont.string ~enc:Fun.id ~enc_case cases
240240+ ~tag_to_string:Fun.id ~tag_compare:String.compare
241241+ |> Jsont.Object.finish
242242+end
+222
proto/permissions.mli
···11+(** Permission system wire format for Claude tool invocations.
22+33+ This module provides the wire format encoding/decoding for permission types
44+ used in the Claude protocol. It handles JSON serialization and
55+ deserialization with proper field name mappings. *)
66+77+(** {1 Permission Modes} *)
88+99+module Mode : sig
1010+ (** Permission modes control the overall behavior of the permission system. *)
1111+1212+ type t =
1313+ | Default (** Standard permission mode with normal checks *)
1414+ | Accept_edits (** Automatically accept file edits *)
1515+ | Plan (** Planning mode with restricted execution *)
1616+ | Bypass_permissions (** Bypass all permission checks *)
1717+ (** The type of permission modes. *)
1818+1919+ val jsont : t Jsont.t
2020+ (** [jsont] is the Jsont codec for permission modes. Wire format uses
2121+ camelCase: "default", "acceptEdits", "plan", "bypassPermissions". *)
2222+2323+ val to_string : t -> string
2424+ (** [to_string t] converts a mode to its wire format string representation. *)
2525+2626+ val of_string : string -> t
2727+ (** [of_string s] parses a mode from its wire format string representation.
2828+ @raise Invalid_argument if the string is not a valid mode. *)
2929+end
3030+3131+(** {1 Permission Behaviors} *)
3232+3333+module Behavior : sig
3434+ (** Behaviors determine how permission requests are handled. *)
3535+3636+ type t =
3737+ | Allow (** Allow the operation *)
3838+ | Deny (** Deny the operation *)
3939+ | Ask (** Ask the user for permission *)
4040+ (** The type of permission behaviors. *)
4141+4242+ val jsont : t Jsont.t
4343+ (** [jsont] is the Jsont codec for permission behaviors. Wire format uses
4444+ lowercase: "allow", "deny", "ask". *)
4545+4646+ val to_string : t -> string
4747+ (** [to_string t] converts a behavior to its wire format string
4848+ representation. *)
4949+5050+ val of_string : string -> t
5151+ (** [of_string s] parses a behavior from its wire format string
5252+ representation.
5353+ @raise Invalid_argument if the string is not a valid behavior. *)
5454+end
5555+5656+(** {1 Permission Rules} *)
5757+5858+module Rule : sig
5959+ (** Rules define specific permissions for tools. *)
6060+6161+ type t
6262+ (** The type of permission rules. *)
6363+6464+ val jsont : t Jsont.t
6565+ (** [jsont] is the Jsont codec for permission rules. Preserves unknown fields
6666+ for forward compatibility. *)
6767+6868+ val create :
6969+ tool_name:string -> ?rule_content:string -> ?unknown:Unknown.t -> unit -> t
7070+ (** [create ~tool_name ?rule_content ?unknown ()] creates a new rule.
7171+ @param tool_name The name of the tool this rule applies to
7272+ @param rule_content Optional rule specification or pattern
7373+ @param unknown Optional unknown fields to preserve *)
7474+7575+ val tool_name : t -> string
7676+ (** [tool_name t] returns the tool name. *)
7777+7878+ val rule_content : t -> string option
7979+ (** [rule_content t] returns the optional rule content. *)
8080+8181+ val unknown : t -> Unknown.t
8282+ (** [unknown t] returns the unknown fields. *)
8383+end
8484+8585+(** {1 Permission Updates} *)
8686+8787+module Update : sig
8888+ (** Updates modify permission settings. *)
8989+9090+ type destination =
9191+ | User_settings (** Apply to user settings *)
9292+ | Project_settings (** Apply to project settings *)
9393+ | Local_settings (** Apply to local settings *)
9494+ | Session (** Apply to current session only *)
9595+ (** The destination for permission updates. *)
9696+9797+ type update_type =
9898+ | Add_rules (** Add new rules *)
9999+ | Replace_rules (** Replace existing rules *)
100100+ | Remove_rules (** Remove rules *)
101101+ | Set_mode (** Set permission mode *)
102102+ | Add_directories (** Add allowed directories *)
103103+ | Remove_directories (** Remove allowed directories *)
104104+ (** The type of permission update. *)
105105+106106+ type t
107107+ (** The type of permission updates. *)
108108+109109+ val jsont : t Jsont.t
110110+ (** [jsont] is the Jsont codec for permission updates. Wire format uses
111111+ camelCase for destination ("userSettings", "projectSettings",
112112+ "localSettings", "session") and update_type ("addRules", "replaceRules",
113113+ "removeRules", "setMode", "addDirectories", "removeDirectories"). *)
114114+115115+ val create :
116116+ update_type:update_type ->
117117+ ?rules:Rule.t list ->
118118+ ?behavior:Behavior.t ->
119119+ ?mode:Mode.t ->
120120+ ?directories:string list ->
121121+ ?destination:destination ->
122122+ ?unknown:Unknown.t ->
123123+ unit ->
124124+ t
125125+ (** [create ~update_type ?rules ?behavior ?mode ?directories ?destination
126126+ ?unknown ()] creates a new permission update.
127127+ @param update_type The type of update to perform
128128+ @param rules Optional list of rules to add/remove/replace
129129+ @param behavior Optional behavior to set
130130+ @param mode Optional permission mode to set
131131+ @param directories Optional directories to add/remove
132132+ @param destination Optional destination for the update
133133+ @param unknown Optional unknown fields to preserve *)
134134+135135+ val update_type : t -> update_type
136136+ (** [update_type t] returns the update type. *)
137137+138138+ val rules : t -> Rule.t list option
139139+ (** [rules t] returns the optional list of rules. *)
140140+141141+ val behavior : t -> Behavior.t option
142142+ (** [behavior t] returns the optional behavior. *)
143143+144144+ val mode : t -> Mode.t option
145145+ (** [mode t] returns the optional mode. *)
146146+147147+ val directories : t -> string list option
148148+ (** [directories t] returns the optional list of directories. *)
149149+150150+ val destination : t -> destination option
151151+ (** [destination t] returns the optional destination. *)
152152+153153+ val unknown : t -> Unknown.t
154154+ (** [unknown t] returns the unknown fields. *)
155155+end
156156+157157+(** {1 Permission Context} *)
158158+159159+module Context : sig
160160+ (** Context provided to permission callbacks. *)
161161+162162+ type t
163163+ (** The type of permission context. *)
164164+165165+ val jsont : t Jsont.t
166166+ (** [jsont] is the Jsont codec for permission context. Preserves unknown
167167+ fields for forward compatibility. *)
168168+169169+ val create : ?suggestions:Update.t list -> ?unknown:Unknown.t -> unit -> t
170170+ (** [create ?suggestions ?unknown ()] creates a new context.
171171+ @param suggestions Optional list of suggested permission updates
172172+ @param unknown Optional unknown fields to preserve *)
173173+174174+ val suggestions : t -> Update.t list
175175+ (** [suggestions t] returns the list of suggested updates. *)
176176+177177+ val unknown : t -> Unknown.t
178178+ (** [unknown t] returns the unknown fields. *)
179179+end
180180+181181+(** {1 Permission Results} *)
182182+183183+module Result : sig
184184+ (** Results of permission checks. *)
185185+186186+ type t =
187187+ | Allow of {
188188+ updated_input : Jsont.json option; (** Modified tool input *)
189189+ updated_permissions : Update.t list option;
190190+ (** Permission updates to apply *)
191191+ unknown : Unknown.t; (** Unknown fields *)
192192+ }
193193+ | Deny of {
194194+ message : string; (** Reason for denial *)
195195+ interrupt : bool; (** Whether to interrupt execution *)
196196+ unknown : Unknown.t; (** Unknown fields *)
197197+ }
198198+ (** The type of permission results. Wire format uses a discriminated union
199199+ with "behavior" field set to "allow" or "deny". *)
200200+201201+ val jsont : t Jsont.t
202202+ (** [jsont] is the Jsont codec for permission results. Preserves unknown
203203+ fields for forward compatibility. *)
204204+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.
213213+ @param updated_input Optional modified tool input
214214+ @param updated_permissions Optional permission updates to apply
215215+ @param unknown Optional unknown fields to preserve *)
216216+217217+ val deny : message:string -> interrupt:bool -> ?unknown:Unknown.t -> unit -> t
218218+ (** [deny ~message ~interrupt ?unknown ()] creates a deny result.
219219+ @param message The reason for denying permission
220220+ @param interrupt Whether to interrupt further execution
221221+ @param unknown Optional unknown fields to preserve *)
222222+end
+12
proto/structured_output.ml
···11+(** Structured output wire format implementation. *)
22+33+type t = { json_schema : Jsont.json }
44+55+let of_json_schema schema = { json_schema = schema }
66+let to_json_schema t = t.json_schema
77+88+(* Codec for serializing structured output format to wire protocol *)
99+let jsont : t Jsont.t =
1010+ Jsont.Object.map ~kind:"StructuredOutput" (fun json_schema -> { json_schema })
1111+ |> Jsont.Object.mem "jsonSchema" Jsont.json ~enc:(fun t -> t.json_schema)
1212+ |> Jsont.Object.finish
+61
proto/structured_output.mli
···11+(** Structured output configuration using JSON Schema.
22+33+ This module provides the wire format types for structured output support,
44+ allowing specification of expected output formats using JSON schemas. When a
55+ structured output format is configured, Claude will return its response in
66+ the specified JSON format, validated against the provided schema.
77+88+ This is the protocol-level module. For the high-level API with logging and
99+ additional features, see {!Claudeio.Structured_output}. *)
1010+1111+(** {1 Output Format Configuration} *)
1212+1313+type t
1414+(** The type of structured output format configurations.
1515+1616+ This wraps a JSON Schema that specifies the expected output format. *)
1717+1818+val of_json_schema : Jsont.json -> t
1919+(** [of_json_schema schema] creates an output format from a JSON Schema.
2020+2121+ The schema should be a valid JSON Schema Draft 7 as a {!Jsont.json} value.
2222+2323+ Example:
2424+ {[
2525+ let meta = Jsont.Meta.none in
2626+ let schema =
2727+ Jsont.Object
2828+ ( [
2929+ (("type", meta), Jsont.String ("object", meta));
3030+ ( ("properties", meta),
3131+ Jsont.Object
3232+ ( [
3333+ ( ("name", meta),
3434+ Jsont.Object
3535+ ([ (("type", meta), Jsont.String ("string", meta)) ], meta)
3636+ );
3737+ ( ("age", meta),
3838+ Jsont.Object
3939+ ([ (("type", meta), Jsont.String ("integer", meta)) ], meta)
4040+ );
4141+ ],
4242+ meta ) );
4343+ ( ("required", meta),
4444+ Jsont.Array
4545+ ([ Jsont.String ("name", meta); Jsont.String ("age", meta) ], meta)
4646+ );
4747+ ],
4848+ meta )
4949+ in
5050+5151+ let format = Structured_output.of_json_schema schema
5252+ ]} *)
5353+5454+val to_json_schema : t -> Jsont.json
5555+(** [to_json_schema t] extracts the JSON Schema from the output format. *)
5656+5757+val jsont : t Jsont.t
5858+(** Codec for structured output format.
5959+6060+ Encodes/decodes the structured output configuration to/from the wire format
6161+ JSON representation used by the Claude CLI protocol. *)
+57
proto/unknown.ml
···11+(** Unknown fields for preserving extra JSON object members during
22+ round-tripping.
33+44+ This module provides an opaque type for storing unknown JSON fields as an
55+ association list. This is useful for preserving fields that are not part of
66+ the defined schema but should be maintained when reading and writing JSON.
77+*)
88+99+type t = (string * Jsont.json) list
1010+1111+let empty = []
1212+let is_empty = function [] -> true | _ -> false
1313+let of_assoc x = x
1414+let to_assoc x = x
1515+1616+let jsont =
1717+ let open Jsont in
1818+ let dec obj =
1919+ match obj with
2020+ | Object (fields, _) ->
2121+ (* Convert from Jsont.mem list (name * json) to (string * json) list *)
2222+ List.map (fun ((name, _meta), json) -> (name, json)) fields
2323+ | _ -> invalid_arg "Expected object"
2424+ in
2525+ let enc fields =
2626+ (* Convert from (string * json) list to Jsont.mem list *)
2727+ let mems =
2828+ List.map (fun (name, json) -> ((name, Meta.none), json)) fields
2929+ in
3030+ Object (mems, Meta.none)
3131+ in
3232+ map ~dec ~enc json
3333+3434+(** Mems codec for use with Jsont.Object.keep_unknown.
3535+3636+ This provides a custom mems codec that converts between our (string *
3737+ Jsont.json) list representation and the Jsont.mem list representation
3838+ used by keep_unknown. *)
3939+let mems : (t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
4040+ let open Jsont in
4141+ (* The decoder builds up a mem list (the third type parameter) and
4242+ dec_finish converts it to our type t *)
4343+ let dec_empty () = [] in
4444+ let dec_add meta name json acc = ((name, meta), json) :: acc in
4545+ let dec_finish _meta mems =
4646+ (* Convert from mem list to (string * json) list *)
4747+ List.rev_map (fun ((name, _meta), json) -> (name, json)) mems
4848+ in
4949+ let enc =
5050+ {
5151+ Object.Mems.enc = (fun k fields acc ->
5252+ List.fold_left
5353+ (fun acc (name, json) -> k Meta.none name json acc)
5454+ acc fields);
5555+ }
5656+ in
5757+ Object.Mems.map ~kind:"Unknown" ~dec_empty ~dec_add ~dec_finish ~enc Jsont.json
+29
proto/unknown.mli
···11+(** Unknown fields for preserving extra JSON object members during
22+ round-tripping.
33+44+ This module provides an opaque type for storing unknown JSON fields as an
55+ association list. This is useful for preserving fields that are not part of
66+ the defined schema but should be maintained when reading and writing JSON.
77+*)
88+99+type t
1010+(** The opaque type of unknown fields, stored as an association list of field
1111+ names to JSON values. *)
1212+1313+val empty : t
1414+(** [empty] is an empty set of unknown fields. *)
1515+1616+val is_empty : t -> bool
1717+(** [is_empty t] returns [true] if there are no unknown fields stored in [t]. *)
1818+1919+val of_assoc : (string * Jsont.json) list -> t
2020+(** [of_assoc assoc] creates unknown fields from an association list. *)
2121+2222+val to_assoc : t -> (string * Jsont.json) list
2323+(** [to_assoc t] returns the association list of unknown fields. *)
2424+2525+val jsont : t Jsont.t
2626+(** [jsont] is a codec for encoding and decoding unknown fields to/from JSON. *)
2727+2828+val mems : (t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map
2929+(** [mems] is a mems codec for use with [Jsont.Object.keep_unknown]. *)
···6677let process_claude_response client name =
88 Log.info (fun m -> m "=== %s's Response ===" name);
99- let messages = Claude.Client.receive_all client in
99+ let responses = Claude.Client.receive_all client in
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);
1111+ (fun resp ->
1212+ match resp with
1313+ | Claude.Response.Text t ->
1414+ let text = Claude.Response.Text.content t in
1515+ Log.app (fun m -> m "%s: %s" name text)
1616+ | Claude.Response.Tool_use t ->
1717+ Log.debug (fun m ->
1818+ m "%s using tool: %s" name (Claude.Response.Tool_use.name t))
1919+ | Claude.Response.Thinking t ->
2920 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 -> ());
2121+ m "%s thinking: %s" name (Claude.Response.Thinking.content t))
2222+ | Claude.Response.Complete c ->
2323+ (if Claude.Response.Complete.total_cost_usd c <> None then
2424+ let cost = Option.get (Claude.Response.Complete.total_cost_usd c) in
2525+ Log.info (fun m -> m "%s's joke cost: $%.6f" name cost));
3926 Log.debug (fun m ->
4027 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 *)
2828+ (Claude.Response.Complete.session_id c)
2929+ (Claude.Response.Complete.duration_ms c))
3030+ | Claude.Response.Error e ->
3131+ Log.err (fun m -> m "Error from %s: %s" name (Claude.Response.Error.message e))
3232+ | Claude.Response.Init _ ->
3333+ (* Init messages are already logged by the library *)
4534 ()
4646- | Claude.Message.User _ ->
4747- (* User messages are already logged by the library *)
3535+ | Claude.Response.Tool_result _ ->
3636+ (* Tool results are user messages, skip *)
4837 ())
4949- messages
3838+ responses
50395140let run_claude ~sw ~env name prompt =
5241 Log.info (fun m -> m "šŖ Starting %s..." name);
5342 let options =
5454- Claude.Options.create
5555- ~model:(Claude.Model.of_string "sonnet")
5656- ~allowed_tools:[] ()
4343+ Claude.Options.default
4444+ |> Claude.Options.with_model (Claude.Model.of_string "sonnet")
4545+ |> Claude.Options.with_allowed_tools []
5746 in
58475948 let client =
+25-30
test/discovery_demo.ml
···66module Log = (val Logs.src_log src : Logs.LOG)
7788let process_response client =
99- let messages = Claude.Client.receive_all client in
99+ let responses = Claude.Client.receive_all client in
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 -> ())
1111+ (fun resp ->
1212+ match resp with
1313+ | Claude.Response.Text text ->
1414+ let content = Claude.Response.Text.content text in
1515+ Log.app (fun m ->
1616+ m "Claude: %s"
1717+ (if String.length content > 100 then
1818+ String.sub content 0 100 ^ "..."
1919+ else content))
2020+ | Claude.Response.Tool_use t ->
2121+ Log.info (fun m ->
2222+ m "Tool use: %s" (Claude.Response.Tool_use.name t))
2323+ | Claude.Response.Error err ->
2424+ Log.err (fun m -> m "Error: %s" (Claude.Response.Error.message err))
2525+ | Claude.Response.Complete result ->
2626+ (match Claude.Response.Complete.total_cost_usd result with
2727+ | Some cost -> Log.info (fun m -> m "Cost: $%.6f" cost)
2828+ | None -> ())
3529 | _ -> ())
3636- messages
3030+ responses
37313832let run_discovery ~sw ~env =
3933 Log.app (fun m -> m "š Permission Discovery Demo");
···42364337 (* Create client with discovery mode *)
4438 let options =
4545- Claude.Options.create ~model:(Claude.Model.of_string "sonnet") ()
3939+ Claude.Options.default
4040+ |> Claude.Options.with_model (Claude.Proto.Model.of_string "sonnet")
4641 in
4742 let client =
4848- Claude.Client.discover_permissions
4949- (Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ())
4343+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ()
5044 in
4545+ Claude.Client.enable_permission_discovery client;
51465247 (* Send a prompt that will need permissions *)
5348 Log.app (fun m -> m "Asking Claude to read a secret file...");
···5752 process_response client;
58535954 (* Check what permissions were requested *)
6060- let permissions = Claude.Client.get_discovered_permissions client in
5555+ let permissions = Claude.Client.discovered_permissions client in
6156 if permissions = [] then
6257 Log.app (fun m ->
6358 m
+14-25
test/dynamic_control_demo.ml
···1818 traceln "1. Initial query with default model";
1919 Client.query client "What model are you?";
20202121- (* Consume initial messages *)
2222- let messages = Client.receive_all client in
2121+ (* Consume initial responses *)
2222+ let responses = Client.receive_all client in
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)
2525+ | Response.Text text ->
2626+ traceln "Assistant: %s" (Response.Text.content text)
3227 | _ -> ())
3333- messages;
2828+ responses;
34293530 traceln "\n2. Getting server info...";
3631 (try
3732 let info = Client.get_server_info client in
3838- traceln "Server version: %s" (Sdk_control.Server_info.version info);
3333+ traceln "Server version: %s" (Claude.Server_info.version info);
3934 traceln "Capabilities: [%s]"
4040- (String.concat ", " (Sdk_control.Server_info.capabilities info));
3535+ (String.concat ", " (Claude.Server_info.capabilities info));
4136 traceln "Commands: [%s]"
4242- (String.concat ", " (Sdk_control.Server_info.commands info));
3737+ (String.concat ", " (Claude.Server_info.commands info));
4338 traceln "Output styles: [%s]"
4444- (String.concat ", " (Sdk_control.Server_info.output_styles info))
3939+ (String.concat ", " (Claude.Server_info.output_styles info))
4540 with
4641 | Failure msg -> traceln "Failed to get server info: %s" msg
4742 | exn -> traceln "Error getting server info: %s" (Printexc.to_string exn));
48434944 traceln "\n3. Switching to a different model (if available)...";
5045 (try
5151- Client.set_model client (Model.of_string "claude-sonnet-4");
4646+ Client.set_model client (Proto.Model.of_string "claude-sonnet-4");
5247 traceln "Model switched successfully";
53485449 (* Query with new model *)
5550 Client.query client "Confirm your model again please.";
5656- let messages = Client.receive_all client in
5151+ let responses = Client.receive_all client in
5752 List.iter
5853 (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)
5454+ | Response.Text text ->
5555+ traceln "Assistant (new model): %s" (Response.Text.content text)
6756 | _ -> ())
6868- messages
5757+ responses
6958 with
7059 | Failure msg -> traceln "Failed to switch model: %s" msg
7160 | exn -> traceln "Error switching model: %s" (Printexc.to_string exn));
+36-63
test/hooks_example.ml
···55module Log = (val Logs.src_log src : Logs.LOG)
6677(* Example 1: Block dangerous bash commands *)
88-let block_dangerous_bash ~input ~tool_use_id:_ ~context:_ =
99- let hook = Claude.Hooks.PreToolUse.of_json input in
1010- let tool_name = Claude.Hooks.PreToolUse.tool_name hook in
1111-1212- if tool_name = "Bash" then
1313- let tool_input = Claude.Hooks.PreToolUse.tool_input hook in
1414- match Test_json_utils.get_string tool_input "command" with
88+let block_dangerous_bash input =
99+ if input.Claude.Hooks.PreToolUse.tool_name = "Bash" then
1010+ match Claude.Tool_input.get_string input.Claude.Hooks.PreToolUse.tool_input "command" with
1511 | Some command ->
1612 if String.length command >= 6 && String.sub command 0 6 = "rm -rf" then begin
1713 Log.app (fun m -> m "š« Blocked dangerous command: %s" command);
1818- let output =
1919- Claude.Hooks.PreToolUse.deny
2020- ~reason:"Command contains dangerous 'rm -rf' pattern" ()
2121- in
2222- Claude.Hooks.continue
2323- ~system_message:"Blocked dangerous rm -rf command"
2424- ~hook_specific_output:
2525- (Claude.Hooks.PreToolUse.output_to_json output)
2626- ()
1414+ Claude.Hooks.PreToolUse.deny
1515+ ~reason:"Command contains dangerous 'rm -rf' pattern" ()
2716 end
2828- else Claude.Hooks.continue ()
2929- | _ -> Claude.Hooks.continue ()
3030- else Claude.Hooks.continue ()
1717+ else Claude.Hooks.PreToolUse.continue ()
1818+ | _ -> Claude.Hooks.PreToolUse.continue ()
1919+ else Claude.Hooks.PreToolUse.continue ()
31203221(* Example 2: Log all tool usage *)
3333-let log_tool_usage ~input ~tool_use_id ~context:_ =
3434- let hook = Claude.Hooks.PreToolUse.of_json input in
3535- let tool_name = Claude.Hooks.PreToolUse.tool_name hook in
3636- let tool_use_id_str = Option.value tool_use_id ~default:"<none>" in
3737- Log.app (fun m -> m "š Tool %s called (ID: %s)" tool_name tool_use_id_str);
3838- Claude.Hooks.continue ()
2222+let log_tool_usage input =
2323+ Log.app (fun m -> m "š Tool %s called" input.Claude.Hooks.PreToolUse.tool_name);
2424+ Claude.Hooks.PreToolUse.continue ()
39254026let run_example ~sw ~env =
4127 Log.app (fun m -> m "š§ Hooks System Example");
···4430 (* Configure hooks *)
4531 let hooks =
4632 Claude.Hooks.empty
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- ]
3333+ |> Claude.Hooks.on_pre_tool_use log_tool_usage
3434+ |> Claude.Hooks.on_pre_tool_use ~pattern:"Bash" block_dangerous_bash
5435 in
55365637 let options =
5757- Claude.Options.create ~model:(Claude.Model.of_string "sonnet") ~hooks ()
3838+ Claude.Options.default
3939+ |> Claude.Options.with_model (Claude.Model.of_string "sonnet")
4040+ |> Claude.Options.with_hooks hooks
5841 in
59426043 let client =
···67506851 let messages = Claude.Client.receive_all client in
6952 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")
5353+ (fun resp ->
5454+ match resp with
5555+ | Claude.Response.Text text ->
5656+ let content = Claude.Response.Text.content text in
5757+ if String.length content > 0 then
5858+ Log.app (fun m -> m "Claude: %s" content)
5959+ | Claude.Response.Complete _ ->
6060+ Log.app (fun m -> m "ā Test 1 complete\n")
6161+ | Claude.Response.Error err ->
6262+ Log.err (fun m -> m "ā Error: %s" (Claude.Response.Error.message err))
8563 | _ -> ())
8664 messages;
8765···91699270 let messages = Claude.Client.receive_all client in
9371 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")
7272+ (fun resp ->
7373+ match resp with
7474+ | Claude.Response.Text text ->
7575+ let content = Claude.Response.Text.content text in
7676+ if String.length content > 0 then
7777+ Log.app (fun m -> m "Claude: %s" content)
7878+ | Claude.Response.Complete _ ->
7979+ Log.app (fun m -> m "ā Test 2 complete")
8080+ | Claude.Response.Error err ->
8181+ Log.err (fun m -> m "ā Error: %s" (Claude.Response.Error.message err))
10982 | _ -> ())
11083 messages;
11184
+42-40
test/permission_demo.ml
···3030end
31313232(* Interactive permission callback *)
3333-let interactive_permission_callback ~tool_name ~input ~context:_ =
3333+let interactive_permission_callback ctx =
3434+ let open Claude.Permissions in
3535+ let tool_name = ctx.tool_name in
3636+ let input = ctx.input in
3737+3438 Log.info (fun m -> m "š Permission callback invoked for tool: %s" tool_name);
3539 Log.app (fun m -> m "\nš PERMISSION REQUEST š");
3640 Log.app (fun m -> m "Tool: %s" tool_name);
37413842 (* Log the full input for debugging *)
3939- Log.info (fun m -> m "Full input JSON: %s" (Test_json_utils.to_string input));
4343+ let input_json = Claude.Tool_input.to_json input in
4444+ Log.info (fun m -> m "Full input JSON: %s" (Test_json_utils.to_string input_json));
40454146 (* Show input details *)
4247 (* Try to extract key information from the input *)
4348 (try
4449 match tool_name with
4550 | "Read" -> (
4646- match Test_json_utils.get_string input "file_path" with
5151+ match Test_json_utils.get_string input_json "file_path" with
4752 | Some file_path -> Log.app (fun m -> m "File: %s" file_path)
4853 | None -> ())
4954 | "Bash" -> (
5050- match Test_json_utils.get_string input "command" with
5555+ match Test_json_utils.get_string input_json "command" with
5156 | Some command -> Log.app (fun m -> m "Command: %s" command)
5257 | None -> ())
5358 | "Write" | "Edit" -> (
5454- match Test_json_utils.get_string input "file_path" with
5959+ match Test_json_utils.get_string input_json "file_path" with
5560 | Some file_path -> Log.app (fun m -> m "File: %s" file_path)
5661 | None -> ())
5762 | "Glob" -> (
5858- match Test_json_utils.get_string input "pattern" with
6363+ match Test_json_utils.get_string input_json "pattern" with
5964 | Some pattern -> (
6065 Log.app (fun m -> m "Pattern: %s" pattern);
6161- match Test_json_utils.get_string input "path" with
6666+ match Test_json_utils.get_string input_json "path" with
6267 | Some path -> Log.app (fun m -> m "Path: %s" path)
6368 | None -> Log.app (fun m -> m "Path: (current directory)"))
6469 | None -> ())
6570 | "Grep" -> (
6666- match Test_json_utils.get_string input "pattern" with
7171+ match Test_json_utils.get_string input_json "pattern" with
6772 | Some pattern -> (
6873 Log.app (fun m -> m "Pattern: %s" pattern);
6969- match Test_json_utils.get_string input "path" with
7474+ match Test_json_utils.get_string input_json "path" with
7075 | Some path -> Log.app (fun m -> m "Path: %s" path)
7176 | None -> Log.app (fun m -> m "Path: (current directory)"))
7277 | None -> ())
7373- | _ -> Log.app (fun m -> m "Input: %s" (Test_json_utils.to_string input))
7878+ | _ -> Log.app (fun m -> m "Input: %s" (Test_json_utils.to_string input_json))
7479 with exn ->
7580 Log.info (fun m ->
7681 m "Failed to parse input details: %s" (Printexc.to_string exn)));
···7984 if Granted.is_granted tool_name then begin
8085 Log.app (fun m -> m "ā Auto-approved (previously granted)");
8186 Log.info (fun m -> m "Returning allow result for %s" tool_name);
8282- Claude.Permissions.Result.allow ()
8787+ Decision.allow ()
8388 end
8489 else begin
8590 (* Ask user - read from /dev/tty since stdin is connected to Claude process *)
···9196 | "y" | "yes" ->
9297 Log.app (fun m -> m "ā Allowed (this time only)");
9398 Log.info (fun m -> m "User approved %s for this request only" tool_name);
9494- Claude.Permissions.Result.allow ()
9999+ Decision.allow ()
95100 | "a" | "always" ->
96101 Granted.grant tool_name;
97102 Log.info (fun m ->
98103 m "User granted permanent permission for %s" tool_name);
9999- Claude.Permissions.Result.allow ()
104104+ Decision.allow ()
100105 | _ ->
101106 Granted.deny tool_name;
102107 Log.info (fun m -> m "User denied permission for %s" tool_name);
103103- Claude.Permissions.Result.deny
108108+ Decision.deny
104109 ~message:(Printf.sprintf "User denied access to %s" tool_name)
105105- ~interrupt:false ()
110110+ ~interrupt:false
106111 end
107112108113let process_response client =
109109- let messages = Claude.Client.receive_all client in
114114+ let responses = Claude.Client.receive_all client in
110115 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
116116+ (fun response ->
117117+ match response with
118118+ | Claude.Response.Text t ->
119119+ let text = Claude.Response.Text.content t in
120120+ Log.app (fun m -> m "\nš Claude says:\n%s" text)
121121+ | Claude.Response.Tool_use t ->
122122+ Log.info (fun m ->
123123+ m "š§ Tool use: %s (id: %s)"
124124+ (Claude.Response.Tool_use.name t)
125125+ (Claude.Response.Tool_use.id t))
126126+ | Claude.Response.Complete c ->
127127+ (if Claude.Response.Complete.result_text c = None then
128128 Log.err (fun m -> m "ā Error occurred!")
129129 else
130130- match Claude.Message.Result.total_cost_usd msg with
130130+ match Claude.Response.Complete.total_cost_usd c 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))
134134+ m "ā±ļø Duration: %dms" (Claude.Response.Complete.duration_ms c))
135135+ | Claude.Response.Error e ->
136136+ Log.err (fun m -> m "ā Error: %s" (Claude.Response.Error.message e))
135137 | _ -> ())
136136- messages
138138+ responses
137139138140let run_demo ~sw ~env =
139141 Log.app (fun m -> m "š Starting Permission Demo");
···145147 (* DON'T specify allowed_tools - let the permission callback handle everything.
146148 The Default permission mode with a callback should send requests for all tools. *)
147149 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 ()
150150+ Claude.Options.default
151151+ |> Claude.Options.with_model (Claude.Model.of_string "sonnet")
152152+ |> Claude.Options.with_permission_mode Claude.Permissions.Mode.Default
153153+ |> Claude.Options.with_permission_callback interactive_permission_callback
152154 in
153155154156 let client =
+36-54
test/simple_permission_test.ml
···55module Log = (val Logs.src_log src : Logs.LOG)
6677(* Auto-allow callback that logs what it sees *)
88-let auto_allow_callback ~tool_name ~input ~context:_ =
88+let auto_allow_callback ctx =
99 Log.app (fun m -> m "\nš Permission callback invoked!");
1010- Log.app (fun m -> m " Tool: %s" tool_name);
1111- Log.app (fun m -> m " Input: %s" (Test_json_utils.to_string input));
1010+ Log.app (fun m -> m " Tool: %s" ctx.Claude.Permissions.tool_name);
1111+ Log.app (fun m -> m " Input: %s" (Test_json_utils.to_string (Claude.Tool_input.to_json ctx.Claude.Permissions.input)));
1212 Log.app (fun m -> m " ā Auto-allowing");
1313- Claude.Permissions.Result.allow ()
1313+ Claude.Permissions.Decision.allow ()
14141515let run_test ~sw ~env =
1616 Log.app (fun m -> m "š§Ŗ Testing Permission Callbacks (Auto-Allow Mode)");
···18181919 (* Create options with permission callback *)
2020 let options =
2121- Claude.Options.create
2222- ~model:(Claude.Model.of_string "sonnet")
2323- ~permission_callback:auto_allow_callback ()
2121+ Claude.Options.default
2222+ |> Claude.Options.with_model (Claude.Model.of_string "sonnet")
2323+ |> Claude.Options.with_permission_callback auto_allow_callback
2424 in
25252626 Log.app (fun m -> m "Creating client with permission callback...");
···4141 let write_used = ref false in
42424343 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
4444+ (fun resp ->
4545+ match resp with
4646+ | Claude.Response.Text text ->
4747+ let content = Claude.Response.Text.content text in
4848+ if String.length content > 0 then
4949+ Log.app (fun m -> m "\nš¬ Claude: %s" content)
5050+ | Claude.Response.Tool_use t ->
5151+ incr tool_count;
5252+ let tool_name = Claude.Response.Tool_use.name t in
5353+ if tool_name = "Write" then write_used := true;
5454+ Log.app (fun m -> m "š§ Tool use #%d: %s" !tool_count tool_name)
5555+ | Claude.Response.Tool_result r ->
5656+ let tool_use_id = Claude.Content_block.Tool_result.tool_use_id r in
5757+ let is_error =
5858+ Claude.Content_block.Tool_result.is_error r
5959+ |> Option.value ~default:false
6060+ in
6161+ if is_error then begin
6262+ Log.app (fun m -> m "\nā ļø Tool result error for %s:" tool_use_id);
6363+ match Claude.Content_block.Tool_result.content r with
6464+ | Some s -> Log.app (fun m -> m " %s" s)
6565+ | None -> ()
6666+ end
6767+ | Claude.Response.Complete result ->
6868+ Log.app (fun m -> m "\nā Success!");
6969+ (match Claude.Response.Complete.total_cost_usd result with
9070 | Some cost -> Log.app (fun m -> m "š° Cost: $%.6f" cost)
9171 | None -> ());
9272 Log.app (fun m ->
9393- m "ā±ļø Duration: %dms" (Claude.Message.Result.duration_ms msg))
7373+ m "ā±ļø Duration: %dms" (Claude.Response.Complete.duration_ms result))
7474+ | Claude.Response.Error err ->
7575+ Log.err (fun m -> m "\nā Error: %s" (Claude.Response.Error.message err))
9476 | _ -> ())
9577 messages;
9678
+47-27
test/simulated_permissions.ml
···4242end
43434444(* Example permission callback *)
4545-let example_permission_callback ~tool_name ~input:_ ~context:_ =
4545+let example_permission_callback ctx =
4646+ let open Claude.Permissions in
4747+ let tool_name = ctx.tool_name in
4848+4649 Log.app (fun m -> m "\nš Permission Request for: %s" tool_name);
47504851 (* Check current state *)
4952 if PermissionState.is_granted tool_name then begin
5053 Log.app (fun m -> m " ā Auto-approved (previously granted)");
5151- Claude.Permissions.Result.allow ()
5454+ Decision.allow ()
5255 end
5356 else if PermissionState.is_denied tool_name then begin
5457 Log.app (fun m -> m " ā Auto-denied (previously denied)");
5555- Claude.Permissions.Result.deny
5858+ Decision.deny
5659 ~message:(Printf.sprintf "Tool %s is blocked by policy" tool_name)
5757- ~interrupt:false ()
6060+ ~interrupt:false
5861 end
5962 else begin
6063 (* Ask user *)
···6265 match read_line () |> String.lowercase_ascii with
6366 | "y" | "yes" ->
6467 Log.app (fun m -> m " ā Allowed (one time)");
6565- Claude.Permissions.Result.allow ()
6868+ Decision.allow ()
6669 | "n" | "no" ->
6770 Log.app (fun m -> m " ā Denied (one time)");
6868- Claude.Permissions.Result.deny
7171+ Decision.deny
6972 ~message:(Printf.sprintf "User denied %s" tool_name)
7070- ~interrupt:false ()
7373+ ~interrupt:false
7174 | "a" | "always" ->
7275 PermissionState.grant tool_name;
7376 Log.app (fun m -> m " ā Allowed (always)");
7474- Claude.Permissions.Result.allow ()
7777+ Decision.allow ()
7578 | "never" ->
7679 PermissionState.deny tool_name;
7780 Log.app (fun m -> m " ā Denied (always)");
7878- Claude.Permissions.Result.deny
8181+ Decision.deny
7982 ~message:(Printf.sprintf "Tool %s permanently blocked" tool_name)
8080- ~interrupt:false ()
8383+ ~interrupt:false
8184 | _ ->
8285 Log.app (fun m -> m " ā Denied (invalid response)");
8383- Claude.Permissions.Result.deny ~message:"Invalid permission response"
8484- ~interrupt:false ()
8686+ Decision.deny ~message:"Invalid permission response"
8787+ ~interrupt:false
8588 end
86898790(* Demonstrate the permission system *)
···91949295 (* Simulate permission requests *)
9396 let tools = [ "Read"; "Write"; "Bash"; "Edit" ] in
9494- let context = Claude.Permissions.Context.create () in
95979698 Log.app (fun m -> m "This demo simulates permission requests.");
9799 Log.app (fun m -> m "You can respond with: y/n/always/never\n");
9810099101 (* Test each tool *)
100102 List.iter
101101- (fun tool ->
103103+ (fun tool_name ->
102104 let input =
103105 let open Jsont in
104106 Object
···107109 ],
108110 Meta.none )
109111 in
110110- let result =
111111- example_permission_callback ~tool_name:tool ~input ~context
112112+ let tool_input = Claude.Tool_input.of_json input in
113113+ let ctx =
114114+ Claude.Permissions.
115115+ {
116116+ tool_name;
117117+ input = tool_input;
118118+ suggested_rules = [];
119119+ }
112120 in
121121+ let decision = example_permission_callback ctx in
113122114123 (* 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))
124124+ if Claude.Permissions.Decision.is_allow decision then
125125+ Log.info (fun m -> m "Result: Permission granted for %s" tool_name)
126126+ else
127127+ match Claude.Permissions.Decision.deny_message decision with
128128+ | Some message ->
129129+ Log.info (fun m ->
130130+ m "Result: Permission denied for %s - %s" tool_name message)
131131+ | None ->
132132+ Log.info (fun m -> m "Result: Permission denied for %s" tool_name))
121133 tools;
122134123135 (* Show final state *)
···129141 Log.app (fun m -> m "====================================\n");
130142131143 let discovered = ref [] in
132132- let callback = Claude.Permissions.discovery_callback discovered in
144144+ let callback = Claude.Permissions.discovery discovered in
133145134146 (* Simulate some tool requests *)
135147 let requests =
···153165 Log.app (fun m -> m "Simulating tool requests with discovery callback...\n");
154166155167 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
168168+ (fun (tool_name, input) ->
169169+ Log.app (fun m -> m " Request: %s" tool_name);
170170+ let tool_input = Claude.Tool_input.of_json input in
171171+ let ctx =
172172+ Claude.Permissions.
173173+ {
174174+ tool_name;
175175+ input = tool_input;
176176+ suggested_rules = [];
177177+ }
178178+ in
179179+ let _ = callback ctx in
160180 ())
161181 requests;
162182
+16-22
test/structured_output_demo.ml
···100100 in
101101102102 (* Create structured output format from the schema *)
103103- let output_format = C.Structured_output.of_json_schema analysis_schema in
103103+ let output_format = Claude.Proto.Structured_output.of_json_schema analysis_schema in
104104105105 (* Configure Claude with structured output *)
106106 let options =
···132132 C.Client.query client prompt;
133133134134 (* Process responses *)
135135- let messages = C.Client.receive client in
135135+ let responses = C.Client.receive client in
136136 Seq.iter
137137 (function
138138- | C.Message.Assistant msg ->
139139- Printf.printf "\nAssistant response:\n";
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 -> (
138138+ | C.Response.Text text ->
139139+ Printf.printf "\nAssistant text:\n";
140140+ Printf.printf " %s\n" (C.Response.Text.content text)
141141+ | C.Response.Tool_use tool ->
142142+ Printf.printf " Using tool: %s\n" (C.Response.Tool_use.name tool)
143143+ | C.Response.Complete result -> (
150144 Printf.printf "\n=== Result ===\n";
151151- Printf.printf "Duration: %dms\n" (C.Message.Result.duration_ms result);
145145+ Printf.printf "Duration: %dms\n" (C.Response.Complete.duration_ms result);
152146 Printf.printf "Cost: $%.4f\n"
153153- (Option.value (C.Message.Result.total_cost_usd result) ~default:0.0);
147147+ (Option.value (C.Response.Complete.total_cost_usd result) ~default:0.0);
154148155149 (* Extract and display structured output *)
156156- match C.Message.Result.structured_output result with
150150+ match C.Response.Complete.structured_output result with
157151 | Some output ->
158152 Printf.printf "\n=== Structured Output ===\n";
159153 Printf.printf "%s\n\n"
···196190 findings
197191 | None -> (
198192 Printf.printf "No structured output received\n";
199199- match C.Message.Result.result result with
193193+ match C.Response.Complete.result_text result with
200194 | Some text -> Printf.printf "Text result: %s\n" text
201195 | None -> ()))
202202- | C.Message.System (C.Message.System.Init _) ->
203203- Printf.printf "Session initialized\n"
204204- | C.Message.System (C.Message.System.Error _) -> ()
196196+ | C.Response.Init _ -> Printf.printf "Session initialized\n"
197197+ | C.Response.Error err ->
198198+ Printf.printf "Error: %s\n" (C.Response.Error.message err)
205199 | _ -> ())
206206- messages;
200200+ responses;
207201208202 Printf.printf "\nDone!\n"
209203
+7-5
test/structured_output_simple.ml
···4444 Meta.none )
4545 in
46464747- let output_format = C.Structured_output.of_json_schema person_schema in
4747+ let output_format = Claude.Proto.Structured_output.of_json_schema person_schema in
48484949 let options =
5050 C.Options.default
···6262 "Tell me about a famous computer scientist. Provide their name, age, and \
6363 occupation in the exact JSON structure I specified.";
64646565- let messages = C.Client.receive_all client in
6565+ let responses = C.Client.receive_all client in
6666 List.iter
6767 (function
6868- | C.Message.Result result -> (
6868+ | C.Response.Complete result -> (
6969 Printf.printf "Response received!\n";
7070- match C.Message.Result.structured_output result with
7070+ match C.Response.Complete.structured_output result with
7171 | Some json ->
7272 Printf.printf "\nStructured Output:\n%s\n"
7373 (Test_json_utils.to_string ~minify:false json)
7474 | None -> Printf.printf "No structured output\n")
7575+ | C.Response.Error err ->
7676+ Printf.printf "Error: %s\n" (C.Response.Error.message err)
7577 | _ -> ())
7676- messages
7878+ responses
77797880let () =
7981 Eio_main.run @@ fun env ->
+14-16
test/test_incoming.ml
···11(** Test the Incoming message codec *)
2233-open Claude
44-53let test_decode_user_message () =
64 let json_str = {|{"type":"user","content":"Hello"}|} in
77- match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
88- | Ok (Incoming.Message (Message.User _)) ->
55+ match Jsont_bytesrw.decode_string' Proto.Incoming.jsont json_str with
66+ | Ok (Proto.Incoming.Message (Proto.Message.User _)) ->
97 print_endline "ā Decoded user message successfully"
108 | Ok _ -> print_endline "ā Wrong message type decoded"
119 | Error err ->
···1614 let json_str =
1715 {|{"type":"assistant","model":"claude-sonnet-4","content":[{"type":"text","text":"Hi"}]}|}
1816 in
1919- match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
2020- | Ok (Incoming.Message (Message.Assistant _)) ->
1717+ match Jsont_bytesrw.decode_string' Proto.Incoming.jsont json_str with
1818+ | Ok (Proto.Incoming.Message (Proto.Message.Assistant _)) ->
2119 print_endline "ā Decoded assistant message successfully"
2220 | Ok _ -> print_endline "ā Wrong message type decoded"
2321 | Error err ->
···2826 let json_str =
2927 {|{"type":"system","subtype":"init","data":{"session_id":"test-123"}}|}
3028 in
3131- match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
3232- | Ok (Incoming.Message (Message.System _)) ->
2929+ match Jsont_bytesrw.decode_string' Proto.Incoming.jsont json_str with
3030+ | Ok (Proto.Incoming.Message (Proto.Message.System _)) ->
3331 print_endline "ā Decoded system message successfully"
3432 | Ok _ -> print_endline "ā Wrong message type decoded"
3533 | Error err ->
···4038 let json_str =
4139 {|{"type":"control_response","response":{"subtype":"success","request_id":"test-req-1"}}|}
4240 in
4343- match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
4444- | Ok (Incoming.Control_response resp) -> (
4141+ match Jsont_bytesrw.decode_string' Proto.Incoming.jsont json_str with
4242+ | Ok (Proto.Incoming.Control_response resp) -> (
4543 match resp.response with
4646- | Sdk_control.Response.Success s ->
4444+ | Proto.Control.Response.Success s ->
4745 if s.request_id = "test-req-1" then
4846 print_endline "ā Decoded control response successfully"
4947 else Printf.printf "ā Wrong request_id: %s\n" s.request_id
5050- | Sdk_control.Response.Error _ ->
4848+ | Proto.Control.Response.Error _ ->
5149 print_endline "ā Got error response instead of success")
5250 | Ok _ -> print_endline "ā Wrong message type decoded"
5351 | Error err ->
···5856 let json_str =
5957 {|{"type":"control_response","response":{"subtype":"error","request_id":"test-req-2","error":"Something went wrong"}}|}
6058 in
6161- match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
6262- | Ok (Incoming.Control_response resp) -> (
5959+ match Jsont_bytesrw.decode_string' Proto.Incoming.jsont json_str with
6060+ | Ok (Proto.Incoming.Control_response resp) -> (
6361 match resp.response with
6464- | Sdk_control.Response.Error e ->
6262+ | Proto.Control.Response.Error e ->
6563 if e.request_id = "test-req-2" && e.error = "Something went wrong"
6664 then print_endline "ā Decoded control error response successfully"
6765 else Printf.printf "ā Wrong error content\n"
6868- | Sdk_control.Response.Success _ ->
6666+ | Proto.Control.Response.Success _ ->
6967 print_endline "ā Got success response instead of error")
7068 | Ok _ -> print_endline "ā Wrong message type decoded"
7169 | Error err ->
+18-24
test/test_permissions.ml
···55module Log = (val Logs.src_log src : Logs.LOG)
6677(* Simple auto-allow permission callback *)
88-let auto_allow_callback ~tool_name ~input:_ ~context:_ =
99- Log.app (fun m -> m "ā Auto-allowing tool: %s" tool_name);
1010- Claude.Permissions.Result.allow ()
88+let auto_allow_callback ctx =
99+ Log.app (fun m -> m "ā Auto-allowing tool: %s" ctx.Claude.Permissions.tool_name);
1010+ Claude.Permissions.Decision.allow ()
11111212let run_test ~sw ~env =
1313 Log.app (fun m -> m "š§Ŗ Testing Permission Callbacks");
···15151616 (* Create options with custom permission callback *)
1717 let options =
1818- Claude.Options.create
1919- ~model:(Claude.Model.of_string "sonnet")
2020- ~permission_callback:auto_allow_callback ()
1818+ Claude.Options.default
1919+ |> Claude.Options.with_model (Claude.Model.of_string "sonnet")
2020+ |> Claude.Options.with_permission_callback auto_allow_callback
2121 in
22222323 Log.app (fun m -> m "Creating client with permission callback...");
···3434 Log.app (fun m -> m "\nšØ Received %d messages" (List.length messages));
35353636 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!");
3737+ (fun resp ->
3838+ match resp with
3939+ | Claude.Response.Text text ->
4040+ Log.app (fun m -> m "Claude: %s" (Claude.Response.Text.content text))
4141+ | Claude.Response.Tool_use t ->
4242+ Log.app (fun m ->
4343+ m "š§ Tool use: %s" (Claude.Response.Tool_use.name t))
4444+ | Claude.Response.Complete result ->
4545+ Log.app (fun m -> m "ā Success!");
5446 Log.app (fun m ->
5555- m "Duration: %dms" (Claude.Message.Result.duration_ms msg))
4747+ m "Duration: %dms" (Claude.Response.Complete.duration_ms result))
4848+ | Claude.Response.Error err ->
4949+ Log.err (fun m -> m "ā Error: %s" (Claude.Response.Error.message err))
5650 | _ -> ())
5751 messages;
5852