···174174module Sdk_control = Sdk_control
175175(** SDK control protocol for dynamic configuration. *)
176176177177+module Incoming = Incoming
178178+(** Discriminated union of all incoming message types from Claude CLI. *)
179179+177180module Structured_output = Structured_output
178181(** Structured output support using JSON Schema. *)
179182
+184-104
claudeio/lib/client.ml
···11let src = Logs.Src.create "claude.client" ~doc:"Claude client"
22module Log = (val Logs.src_log src : Logs.LOG)
3344+(* Helper functions for JSON manipulation *)
55+let json_to_string json =
66+ match Jsont_bytesrw.encode_string' Jsont.json json with
77+ | Ok s -> s
88+ | Error err -> failwith (Jsont.Error.to_string err)
99+1010+let json_of_string s =
1111+ match Jsont_bytesrw.decode_string' Jsont.json s with
1212+ | Ok j -> j
1313+ | Error err -> failwith (Jsont.Error.to_string err)
1414+1515+let get_field json key =
1616+ match json with
1717+ | Jsont.Object (members, _) ->
1818+ List.find_map (fun ((name, _), value) ->
1919+ if name = key then Some value else None
2020+ ) members
2121+ | _ -> None
2222+2323+let rec find json path =
2424+ match path with
2525+ | [] -> json
2626+ | key :: rest ->
2727+ match get_field json key with
2828+ | Some value -> find value rest
2929+ | None -> raise Not_found
3030+3131+let find_string json path =
3232+ let value = find json path in
3333+ match value with
3434+ | Jsont.String (s, _) -> s
3535+ | _ -> raise (Invalid_argument "Expected string value")
3636+3737+let json_string s = Jsont.String (s, Jsont.Meta.none)
3838+let json_null = Jsont.Null ((), Jsont.Meta.none)
3939+4040+let json_dict pairs =
4141+ let members = List.map (fun (k, v) -> ((k, Jsont.Meta.none), v)) pairs in
4242+ Jsont.Object (members, Jsont.Meta.none)
4343+444type t = {
545 transport : Transport.t;
646 permission_callback : Permissions.callback option;
···848 hook_callbacks : (string, Hooks.callback) Hashtbl.t;
949 mutable next_callback_id : int;
1050 mutable session_id : string option;
1111- control_responses : (string, Ezjsonm.value) Hashtbl.t;
5151+ control_responses : (string, Jsont.json) Hashtbl.t;
1252 control_mutex : Eio.Mutex.t;
1353 control_condition : Eio.Condition.t;
1454}
15551656let handle_control_request t control_msg =
1717- let open Ezjsonm in
1857 let data = Control.data control_msg in
1958 Log.info (fun m -> m "Handling control request: %s" (Control.subtype control_msg));
2020- Log.info (fun m -> m "Control request data: %s" (value_to_string data));
2121- match Json_utils.find_string data ["request"; "subtype"] with
5959+ Log.info (fun m -> m "Control request data: %s" (json_to_string data));
6060+ match find_string data ["request"; "subtype"] with
2261 | "can_use_tool" ->
2323- let tool_name = Json_utils.find_string data ["request"; "tool_name"] in
6262+ let tool_name = find_string data ["request"; "tool_name"] in
2463 let input = find data ["request"; "input"] in
2564 Log.info (fun m -> m "Permission request for tool '%s' with input: %s"
2626- tool_name (value_to_string input));
6565+ tool_name (json_to_string input));
2766 let suggestions =
2867 try
2968 let sugg_json = find data ["request"; "permission_suggestions"] in
3069 match sugg_json with
3131- | `A _ ->
7070+ | Jsont.Array _ ->
3271 (* TODO: Parse permission suggestions *)
3372 []
3473 | _ -> []
···52915392 (* Convert permission result to CLI format: {"behavior": "allow", "updatedInput": ...} or {"behavior": "deny", "message": ...} *)
5493 let response_data = match result with
5555- | Permissions.Result.Allow { updated_input; updated_permissions = _ } ->
9494+ | Permissions.Result.Allow { updated_input; updated_permissions = _; unknown = _ } ->
5695 (* updatedInput is required when allowing - use original input if not modified *)
5796 let updated_input = match updated_input with
5897 | Some inp -> inp
5998 | None -> input (* Use original input *)
6099 in
6161- dict [
6262- ("behavior", string "allow");
100100+ json_dict [
101101+ ("behavior", json_string "allow");
63102 ("updatedInput", updated_input);
64103 ]
6565- | Permissions.Result.Deny { message; interrupt = _ } ->
6666- dict [
6767- ("behavior", string "deny");
6868- ("message", string message);
104104+ | Permissions.Result.Deny { message; interrupt = _; unknown = _ } ->
105105+ json_dict [
106106+ ("behavior", json_string "deny");
107107+ ("message", json_string message);
69108 ]
70109 in
711107272- let response = dict [
7373- "type", string "control_response";
7474- "response", dict [
7575- "subtype", string "success";
7676- "request_id", string (Control.request_id control_msg);
111111+ let response = json_dict [
112112+ "type", json_string "control_response";
113113+ "response", json_dict [
114114+ "subtype", json_string "success";
115115+ "request_id", json_string (Control.request_id control_msg);
77116 "response", response_data
78117 ]
79118 ] in
8080- Log.info (fun m -> m "Sending control response: %s" (value_to_string response));
119119+ Log.info (fun m -> m "Sending control response: %s" (json_to_string response));
81120 Transport.send t.transport response
8212183122 | "hook_callback" ->
8484- let callback_id = Json_utils.find_string data ["request"; "callback_id"] in
123123+ let callback_id = find_string data ["request"; "callback_id"] in
85124 let input = find data ["request"; "input"] in
86125 let tool_use_id =
8787- try Some (Json_utils.find_string data ["request"; "tool_use_id"])
126126+ try Some (find_string data ["request"; "tool_use_id"])
88127 with Not_found -> None
89128 in
90129 Log.info (fun m -> m "Hook callback request for callback_id: %s" callback_id);
···94133 let context = Hooks.Context.create () in
95134 let result = callback ~input ~tool_use_id ~context in
961359797- let response = dict [
9898- "type", string "control_response";
9999- "response", dict [
100100- "subtype", string "success";
101101- "request_id", string (Control.request_id control_msg);
102102- "response", Hooks.result_to_json result
136136+ let result_json = match Jsont.Json.encode Hooks.result_jsont result with
137137+ | Ok j -> j
138138+ | Error msg -> failwith ("Failed to encode hook result: " ^ msg)
139139+ in
140140+141141+ let response = json_dict [
142142+ "type", json_string "control_response";
143143+ "response", json_dict [
144144+ "subtype", json_string "success";
145145+ "request_id", json_string (Control.request_id control_msg);
146146+ "response", result_json
103147 ]
104148 ] in
105149 Log.info (fun m -> m "Hook callback succeeded, sending response");
···108152 | Not_found ->
109153 let error_msg = Printf.sprintf "Hook callback not found: %s" callback_id in
110154 Log.err (fun m -> m "%s" error_msg);
111111- let response = dict [
112112- "type", string "control_response";
113113- "response", dict [
114114- "subtype", string "error";
115115- "request_id", string (Control.request_id control_msg);
116116- "error", string error_msg
155155+ let response = json_dict [
156156+ "type", json_string "control_response";
157157+ "response", json_dict [
158158+ "subtype", json_string "error";
159159+ "request_id", json_string (Control.request_id control_msg);
160160+ "error", json_string error_msg
117161 ]
118162 ] in
119163 Transport.send t.transport response
120164 | exn ->
121165 let error_msg = Printf.sprintf "Hook callback error: %s" (Printexc.to_string exn) in
122166 Log.err (fun m -> m "%s" error_msg);
123123- let response = dict [
124124- "type", string "control_response";
125125- "response", dict [
126126- "subtype", string "error";
127127- "request_id", string (Control.request_id control_msg);
128128- "error", string error_msg
167167+ let response = json_dict [
168168+ "type", json_string "control_response";
169169+ "response", json_dict [
170170+ "subtype", json_string "error";
171171+ "request_id", json_string (Control.request_id control_msg);
172172+ "error", json_string error_msg
129173 ]
130174 ] in
131175 Transport.send t.transport response)
132176133177 | subtype ->
134178 (* Respond with error for unknown control requests *)
135135- let response = dict [
136136- "type", string "control_response";
137137- "response", dict [
138138- "subtype", string "error";
139139- "request_id", string (Control.request_id control_msg);
140140- "error", string (Printf.sprintf "Unsupported control request: %s" subtype)
179179+ let response = json_dict [
180180+ "type", json_string "control_response";
181181+ "response", json_dict [
182182+ "subtype", json_string "error";
183183+ "request_id", json_string (Control.request_id control_msg);
184184+ "error", json_string (Printf.sprintf "Unsupported control request: %s" subtype)
141185 ]
142186 ] in
143187 Transport.send t.transport response
144188189189+let handle_control_response t control_resp =
190190+ let request_id = match control_resp.Sdk_control.response with
191191+ | Sdk_control.Response.Success s -> s.request_id
192192+ | Sdk_control.Response.Error e -> e.request_id
193193+ in
194194+ Log.debug (fun m -> m "Received control response for request_id: %s" request_id);
195195+196196+ (* Store the response as JSON and signal waiting threads *)
197197+ let json = match Jsont.Json.encode Sdk_control.control_response_jsont control_resp with
198198+ | Ok j -> j
199199+ | Error err -> failwith ("Failed to encode control response: " ^ err)
200200+ in
201201+ Eio.Mutex.use_rw ~protect:false t.control_mutex (fun () ->
202202+ Hashtbl.replace t.control_responses request_id json;
203203+ Eio.Condition.broadcast t.control_condition
204204+ )
205205+145206let handle_messages t =
146207 let rec loop () =
147208 match Transport.receive_line t.transport with
148148- | None ->
209209+ | None ->
149210 (* EOF *)
150211 Log.debug (fun m -> m "Handle messages: EOF received");
151212 Seq.Nil
152213 | Some line ->
153214 try
154154- let json = Ezjsonm.value_from_string line in
155155-156156- (* Check if it's a control request or response *)
157157- match Json_utils.find_string json ["type"] with
215215+ (* First check if it's a control_request (special case, not in Incoming) *)
216216+ let json = json_of_string line in
217217+ match find_string json ["type"] with
158218 | "control_request" ->
159219 let control_msg = Control.create
160160- ~request_id:(Json_utils.find_string json ["request_id"])
161161- ~subtype:(Json_utils.find_string json ["request"; "subtype"])
220220+ ~request_id:(find_string json ["request_id"])
221221+ ~subtype:(find_string json ["request"; "subtype"])
162222 ~data:json in
163163- Log.info (fun m -> m "🎯 Received control request: %s (request_id: %s)"
223223+ Log.info (fun m -> m "Received control request: %s (request_id: %s)"
164224 (Control.subtype control_msg) (Control.request_id control_msg));
165225 handle_control_request t control_msg;
166226 loop ()
167227168168- | "control_response" ->
169169- (* Handle control responses (e.g., initialize response) *)
170170- let request_id = Json_utils.find_string json ["response"; "request_id"] in
171171- Log.debug (fun m -> m "Received control response for request_id: %s" request_id);
172172- (* Store the response and signal waiting threads *)
173173- Eio.Mutex.use_rw ~protect:false t.control_mutex (fun () ->
174174- Hashtbl.replace t.control_responses request_id json;
175175- Eio.Condition.broadcast t.control_condition
176176- );
177177- loop ()
178178-179228 | _ ->
180180- (* Regular message *)
181181- let msg = Message.of_json json in
182182- Log.info (fun m -> m "← %a" Message.pp msg);
183183-184184- (* Extract session ID from system messages *)
185185- (match msg with
186186- | Message.System sys when Message.System.subtype sys = "init" ->
187187- (match Message.System.Data.get_string (Message.System.data sys) "session_id" with
188188- | Some session_id ->
189189- t.session_id <- Some session_id;
190190- Log.debug (fun m -> m "Stored session ID: %s" session_id)
191191- | None -> ())
192192- | _ -> ());
193193-194194- Seq.Cons (msg, loop)
229229+ (* Use Incoming codec for all other message types *)
230230+ match Jsont_bytesrw.decode_string' Incoming.jsont line with
231231+ | Ok (Incoming.Message msg) ->
232232+ Log.info (fun m -> m "← %a" Message.pp msg);
233233+234234+ (* Extract session ID from system messages *)
235235+ (match msg with
236236+ | Message.System sys when Message.System.subtype sys = "init" ->
237237+ (match Message.System.Data.get_string (Message.System.data sys) "session_id" with
238238+ | Some session_id ->
239239+ t.session_id <- Some session_id;
240240+ Log.debug (fun m -> m "Stored session ID: %s" session_id)
241241+ | None -> ())
242242+ | _ -> ());
243243+244244+ Seq.Cons (msg, loop)
245245+246246+ | Ok (Incoming.Control_response resp) ->
247247+ handle_control_response t resp;
248248+ loop ()
249249+250250+ | Error err ->
251251+ Log.err (fun m -> m "Failed to decode incoming message: %s\nLine: %s"
252252+ (Jsont.Error.to_string err) line);
253253+ loop ()
195254 with
196255 | exn ->
197197- Log.err (fun m -> m "Failed to parse message: %s\nLine: %s"
256256+ Log.err (fun m -> m "Failed to parse message: %s\nLine: %s"
198257 (Printexc.to_string exn) line);
199258 loop ()
200259 in
···245304 Log.debug (fun m -> m "Registered callback: %s for event: %s" callback_id event_name);
246305 callback_id
247306 ) matcher.Hooks.callbacks in
248248- Ezjsonm.dict [
307307+ json_dict [
249308 "matcher", (match matcher.Hooks.matcher with
250250- | Some p -> Ezjsonm.string p
251251- | None -> `Null);
252252- "hookCallbackIds", `A (List.map (fun id -> Ezjsonm.string id) callback_ids);
309309+ | Some p -> json_string p
310310+ | None -> json_null);
311311+ "hookCallbackIds", Jsont.Array (List.map (fun id -> json_string id) callback_ids, Jsont.Meta.none);
253312 ]
254313 ) matchers in
255255- (event_name, `A matchers_json) :: acc
314314+ (event_name, Jsont.Array (matchers_json, Jsont.Meta.none)) :: acc
256315 ) [] hooks_config in
257316258317 (* Send initialize control request *)
259259- let initialize_msg = Ezjsonm.dict [
260260- "type", Ezjsonm.string "control_request";
261261- "request_id", Ezjsonm.string "init_hooks";
262262- "request", Ezjsonm.dict [
263263- "subtype", Ezjsonm.string "initialize";
264264- "hooks", Ezjsonm.dict hooks_json;
318318+ let initialize_msg = json_dict [
319319+ "type", json_string "control_request";
320320+ "request_id", json_string "init_hooks";
321321+ "request", json_dict [
322322+ "subtype", json_string "initialize";
323323+ "hooks", json_dict hooks_json;
265324 ]
266325 ] in
267326 Log.info (fun m -> m "Sending hooks initialize request");
···274333let query t prompt =
275334 let msg = Message.user_string prompt in
276335 Log.info (fun m -> m "→ %a" Message.pp msg);
277277- Transport.send t.transport (Message.to_json msg)
336336+ let json = match Jsont.Json.encode Message.jsont msg with
337337+ | Ok j -> j
338338+ | Error err -> failwith ("Failed to encode message: " ^ err)
339339+ in
340340+ Transport.send t.transport json
278341279342let send_message t msg =
280343 Log.info (fun m -> m "→ %a" Message.pp msg);
281281- Transport.send t.transport (Message.to_json msg)
344344+ let json = match Jsont.Json.encode Message.jsont msg with
345345+ | Ok j -> j
346346+ | Error err -> failwith ("Failed to encode message: " ^ err)
347347+ in
348348+ Transport.send t.transport json
282349283350let send_user_message t user_msg =
284351 let msg = Message.User user_msg in
285352 Log.info (fun m -> m "→ %a" Message.pp msg);
286286- Transport.send t.transport (Message.User.to_json user_msg)
353353+ let json = match Jsont.Json.encode Message.User.jsont user_msg with
354354+ | Ok j -> j
355355+ | Error err -> failwith ("Failed to encode user message: " ^ err)
356356+ in
357357+ Transport.send t.transport json
287358288359let receive t =
289360 handle_messages t
···323394324395(* Helper to send a control request and wait for response *)
325396let send_control_request t ~request_id request =
326326- let open Ezjsonm in
327397 (* Send the control request *)
328328- let control_msg = Sdk_control.create_request ~request_id ~request in
329329- let json = Sdk_control.to_json control_msg in
330330- Log.info (fun m -> m "Sending control request: %s" (value_to_string json));
398398+ let control_msg = Sdk_control.create_request ~request_id ~request () in
399399+ let json = match Jsont.Json.encode Sdk_control.jsont control_msg with
400400+ | Ok j -> j
401401+ | Error msg -> failwith ("Failed to encode control request: " ^ msg)
402402+ in
403403+ Log.info (fun m -> m "Sending control request: %s" (json_to_string json));
331404 Transport.send t.transport json;
332405333406 (* Wait for the response with timeout *)
···354427 in
355428356429 let response_json = wait_for_response () in
357357- Log.debug (fun m -> m "Received control response: %s" (value_to_string response_json));
430430+ Log.debug (fun m -> m "Received control response: %s" (json_to_string response_json));
358431359432 (* Parse the response *)
360360- let response = find response_json ["response"] |> Sdk_control.Response.of_json in
433433+ let response_data = find response_json ["response"] in
434434+ let response = match Jsont.Json.decode Sdk_control.Response.jsont response_data with
435435+ | Ok r -> r
436436+ | Error msg -> raise (Invalid_argument ("Failed to decode response: " ^ msg))
437437+ in
361438 match response with
362439 | Sdk_control.Response.Success s -> s.response
363440 | Sdk_control.Response.Error e ->
···365442366443let set_permission_mode t mode =
367444 let request_id = Printf.sprintf "set_perm_mode_%f" (Unix.gettimeofday ()) in
368368- let request = Sdk_control.Request.set_permission_mode ~mode in
445445+ let request = Sdk_control.Request.set_permission_mode ~mode () in
369446 let _response = send_control_request t ~request_id request in
370447 Log.info (fun m -> m "Permission mode set to: %a" Permissions.Mode.pp mode)
371448372449let set_model t model =
373450 let model_str = Model.to_string model in
374451 let request_id = Printf.sprintf "set_model_%f" (Unix.gettimeofday ()) in
375375- let request = Sdk_control.Request.set_model ~model:model_str in
452452+ let request = Sdk_control.Request.set_model ~model:model_str () in
376453 let _response = send_control_request t ~request_id request in
377454 Log.info (fun m -> m "Model set to: %a" Model.pp model)
378455···384461 let request = Sdk_control.Request.get_server_info () in
385462 match send_control_request t ~request_id request with
386463 | Some response_data ->
387387- let server_info = Sdk_control.Server_info.of_json response_data in
464464+ let server_info = match Jsont.Json.decode Sdk_control.Server_info.jsont response_data with
465465+ | Ok si -> si
466466+ | Error msg -> raise (Invalid_argument ("Failed to decode server info: " ^ msg))
467467+ in
388468 Log.info (fun m -> m "Retrieved server info: %a" Sdk_control.Server_info.pp server_info);
389469 server_info
390470 | None ->
+215-130
claudeio/lib/content_block.ml
···11-open Ezjsonm
22-module JU = Json_utils
33-41let src = Logs.Src.create "claude.content_block" ~doc:"Claude content blocks"
52module Log = (val Logs.src_log src : Logs.LOG)
637485module Text = struct
99- type t = { text : string }
1010-1111- let create text = { text }
66+ module Unknown = struct
77+ type t = Jsont.json
88+ let empty = Jsont.Object ([], Jsont.Meta.none)
99+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
1010+ let jsont = Jsont.json
1111+ end
1212+1313+ type t = {
1414+ text : string;
1515+ unknown : Unknown.t;
1616+ }
1717+1818+ let create text = { text; unknown = Unknown.empty }
1919+2020+ let make text unknown = { text; unknown }
1221 let text t = t.text
1313-1414- let to_json t =
1515- `O [("type", `String "text"); ("text", `String t.text)]
1616-1717- let of_json = function
1818- | `O fields ->
1919- let text = JU.assoc_string "text" fields in
2020- { text }
2121- | _ -> raise (Invalid_argument "Text.of_json: expected object")
2222-2323- let pp fmt t =
2222+ let unknown t = t.unknown
2323+2424+ let jsont : t Jsont.t =
2525+ Jsont.Object.map ~kind:"Text" make
2626+ |> Jsont.Object.mem "text" Jsont.string ~enc:text
2727+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
2828+ |> Jsont.Object.finish
2929+3030+ let to_json t =
3131+ match Jsont.Json.encode jsont t with
3232+ | Ok json -> json
3333+ | Error msg -> failwith ("Text.to_json: " ^ msg)
3434+3535+ let of_json json =
3636+ match Jsont.Json.decode jsont json with
3737+ | Ok v -> v
3838+ | Error msg -> raise (Invalid_argument ("Text.of_json: " ^ msg))
3939+4040+ let pp fmt t =
2441 if String.length t.text > 60 then
2542 let truncated = String.sub t.text 0 57 in
2643 Fmt.pf fmt "Text[%s...]" truncated
···30473148module Tool_use = struct
3249 module Input = struct
3333- type t = value
3434-5050+ type t = Jsont.json
5151+5252+ let jsont = Jsont.json
5353+3554 let of_string_pairs pairs =
3636- `O (List.map (fun (k, v) -> (k, `String v)) pairs)
3737-3838- let of_assoc assoc = `O assoc
3939-4040- let get_string t key = JU.get_field_string_opt t key
4141-4242- let get_int t key = JU.get_field_int_opt t key
4343-4444- let get_bool t key = JU.get_field_bool_opt t key
4545-4646- let get_float t key = JU.get_field_float_opt t key
4747-5555+ Jsont.Object (
5656+ List.map (fun (k, v) ->
5757+ ((Jsont.Json.name k), Jsont.String (v, Jsont.Meta.none))
5858+ ) pairs,
5959+ Jsont.Meta.none
6060+ )
6161+6262+ let of_assoc (assoc : (string * Jsont.json) list) : t =
6363+ Jsont.Object (
6464+ List.map (fun (k, v) -> (Jsont.Json.name k, v)) assoc,
6565+ Jsont.Meta.none
6666+ )
6767+6868+ let get_field t key =
6969+ match t with
7070+ | Jsont.Object (members, _) ->
7171+ List.find_map (fun ((name, _), value) ->
7272+ if name = key then Some value else None
7373+ ) members
7474+ | _ -> None
7575+7676+ let get_string t key =
7777+ match get_field t key with
7878+ | Some (Jsont.String (s, _)) -> Some s
7979+ | _ -> None
8080+8181+ let get_int t key =
8282+ match get_field t key with
8383+ | Some (Jsont.Number (f, _)) ->
8484+ let i = int_of_float f in
8585+ if float_of_int i = f then Some i else None
8686+ | _ -> None
8787+8888+ let get_bool t key =
8989+ match get_field t key with
9090+ | Some (Jsont.Bool (b, _)) -> Some b
9191+ | _ -> None
9292+9393+ let get_float t key =
9494+ match get_field t key with
9595+ | Some (Jsont.Number (f, _)) -> Some f
9696+ | _ -> None
9797+4898 let keys t =
4999 match t with
5050- | `O fields -> List.map fst fields
100100+ | Jsont.Object (members, _) -> List.map (fun ((name, _), _) -> name) members
51101 | _ -> []
5252-102102+53103 let to_json t = t
54104 let of_json json = json
55105 end
5656-106106+107107+ module Unknown = struct
108108+ type t = Jsont.json
109109+ let empty = Jsont.Object ([], Jsont.Meta.none)
110110+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
111111+ let jsont = Jsont.json
112112+ end
113113+57114 type t = {
58115 id : string;
59116 name : string;
60117 input : Input.t;
118118+ unknown : Unknown.t;
61119 }
6262-6363- let create ~id ~name ~input = { id; name; input }
120120+121121+ let create ~id ~name ~input = { id; name; input; unknown = Unknown.empty }
122122+123123+ let make id name input unknown = { id; name; input; unknown }
64124 let id t = t.id
65125 let name t = t.name
66126 let input t = t.input
6767-127127+ let unknown t = t.unknown
128128+129129+ let jsont : t Jsont.t =
130130+ Jsont.Object.map ~kind:"Tool_use" make
131131+ |> Jsont.Object.mem "id" Jsont.string ~enc:id
132132+ |> Jsont.Object.mem "name" Jsont.string ~enc:name
133133+ |> Jsont.Object.mem "input" Input.jsont ~enc:input
134134+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
135135+ |> Jsont.Object.finish
136136+68137 let to_json t =
6969- `O [
7070- ("type", `String "tool_use");
7171- ("id", `String t.id);
7272- ("name", `String t.name);
7373- ("input", Input.to_json t.input);
7474- ]
7575-7676- let of_json = function
7777- | `O fields ->
7878- let id = JU.assoc_string "id" fields in
7979- let name = JU.assoc_string "name" fields in
8080- let input = Input.of_json (List.assoc "input" fields) in
8181- { id; name; input }
8282- | _ -> raise (Invalid_argument "Tool_use.of_json: expected object")
8383-138138+ match Jsont.Json.encode jsont t with
139139+ | Ok json -> json
140140+ | Error msg -> failwith ("Tool_use.to_json: " ^ msg)
141141+142142+ let of_json json =
143143+ match Jsont.Json.decode jsont json with
144144+ | Ok v -> v
145145+ | Error msg -> raise (Invalid_argument ("Tool_use.of_json: " ^ msg))
146146+84147 let pp fmt t =
85148 let keys = Input.keys t.input in
86149 let key_info = match keys with
···92155end
9315694157module Tool_result = struct
158158+ module Unknown = struct
159159+ type t = Jsont.json
160160+ let empty = Jsont.Object ([], Jsont.Meta.none)
161161+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
162162+ let jsont = Jsont.json
163163+ end
164164+95165 type t = {
96166 tool_use_id : string;
97167 content : string option;
98168 is_error : bool option;
169169+ unknown : Unknown.t;
99170 }
100100-101101- let create ~tool_use_id ?content ?is_error () =
102102- { tool_use_id; content; is_error }
103103-171171+172172+ let create ~tool_use_id ?content ?is_error () =
173173+ { tool_use_id; content; is_error; unknown = Unknown.empty }
174174+175175+ let make tool_use_id content is_error unknown =
176176+ { tool_use_id; content; is_error; unknown }
104177 let tool_use_id t = t.tool_use_id
105178 let content t = t.content
106179 let is_error t = t.is_error
107107-180180+ let unknown t = t.unknown
181181+182182+ let jsont : t Jsont.t =
183183+ Jsont.Object.map ~kind:"Tool_result" make
184184+ |> Jsont.Object.mem "tool_use_id" Jsont.string ~enc:tool_use_id
185185+ |> Jsont.Object.opt_mem "content" Jsont.string ~enc:content
186186+ |> Jsont.Object.opt_mem "is_error" Jsont.bool ~enc:is_error
187187+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
188188+ |> Jsont.Object.finish
189189+108190 let to_json t =
109109- let fields = [
110110- ("type", `String "tool_result");
111111- ("tool_use_id", `String t.tool_use_id);
112112- ] in
113113- let fields = match t.content with
114114- | Some c -> ("content", `String c) :: fields
115115- | None -> fields
116116- in
117117- let fields = match t.is_error with
118118- | Some e -> ("is_error", `Bool e) :: fields
119119- | None -> fields
120120- in
121121- `O fields
122122-123123- let of_json = function
124124- | `O fields ->
125125- let tool_use_id = JU.assoc_string "tool_use_id" fields in
126126- let content =
127127- match List.assoc_opt "content" fields with
128128- | Some (`String s) -> Some s
129129- | Some (`A blocks) ->
130130- (* Handle content as array of blocks - extract text *)
131131- let texts = List.filter_map (function
132132- | `O block_fields ->
133133- (match List.assoc_opt "type" block_fields with
134134- | Some (`String "text") ->
135135- (match List.assoc_opt "text" block_fields with
136136- | Some (`String text) -> Some text
137137- | _ -> None)
138138- | _ -> None)
139139- | _ -> None
140140- ) blocks in
141141- if texts = [] then None else Some (String.concat "\n" texts)
142142- | _ -> None
143143- in
144144- let is_error = JU.assoc_bool_opt "is_error" fields in
145145- { tool_use_id; content; is_error }
146146- | _ -> raise (Invalid_argument "Tool_result.of_json: expected object")
147147-191191+ match Jsont.Json.encode jsont t with
192192+ | Ok json -> json
193193+ | Error msg -> failwith ("Tool_result.to_json: " ^ msg)
194194+195195+ let of_json json =
196196+ match Jsont.Json.decode jsont json with
197197+ | Ok v -> v
198198+ | Error msg -> raise (Invalid_argument ("Tool_result.of_json: " ^ msg))
199199+148200 let pp fmt t =
149201 match t.is_error, t.content with
150150- | Some true, Some c ->
202202+ | Some true, Some c ->
151203 if String.length c > 40 then
152204 let truncated = String.sub c 0 37 in
153205 Fmt.pf fmt "ToolResult[error: %s...]" truncated
···163215end
164216165217module Thinking = struct
218218+ module Unknown = struct
219219+ type t = Jsont.json
220220+ let empty = Jsont.Object ([], Jsont.Meta.none)
221221+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
222222+ let jsont = Jsont.json
223223+ end
224224+166225 type t = {
167226 thinking : string;
168227 signature : string;
228228+ unknown : Unknown.t;
169229 }
170170-171171- let create ~thinking ~signature = { thinking; signature }
230230+231231+ let create ~thinking ~signature = { thinking; signature; unknown = Unknown.empty }
232232+233233+ let make thinking signature unknown = { thinking; signature; unknown }
172234 let thinking t = t.thinking
173235 let signature t = t.signature
174174-236236+ let unknown t = t.unknown
237237+238238+ let jsont : t Jsont.t =
239239+ Jsont.Object.map ~kind:"Thinking" make
240240+ |> Jsont.Object.mem "thinking" Jsont.string ~enc:thinking
241241+ |> Jsont.Object.mem "signature" Jsont.string ~enc:signature
242242+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
243243+ |> Jsont.Object.finish
244244+175245 let to_json t =
176176- `O [
177177- ("type", `String "thinking");
178178- ("thinking", `String t.thinking);
179179- ("signature", `String t.signature);
180180- ]
181181-182182- let of_json = function
183183- | `O fields ->
184184- let thinking = JU.assoc_string "thinking" fields in
185185- let signature = JU.assoc_string "signature" fields in
186186- { thinking; signature }
187187- | _ -> raise (Invalid_argument "Thinking.of_json: expected object")
188188-246246+ match Jsont.Json.encode jsont t with
247247+ | Ok json -> json
248248+ | Error msg -> failwith ("Thinking.to_json: " ^ msg)
249249+250250+ let of_json json =
251251+ match Jsont.Json.decode jsont json with
252252+ | Ok v -> v
253253+ | Error msg -> raise (Invalid_argument ("Thinking.of_json: " ^ msg))
254254+189255 let pp fmt t =
190256 if String.length t.thinking > 50 then
191257 let truncated = String.sub t.thinking 0 47 in
···202268203269let text s = Text (Text.create s)
204270let tool_use ~id ~name ~input = Tool_use (Tool_use.create ~id ~name ~input)
205205-let tool_result ~tool_use_id ?content ?is_error () =
271271+let tool_result ~tool_use_id ?content ?is_error () =
206272 Tool_result (Tool_result.create ~tool_use_id ?content ?is_error ())
207207-let thinking ~thinking ~signature =
273273+let thinking ~thinking ~signature =
208274 Thinking (Thinking.create ~thinking ~signature)
209275210210-let to_json = function
211211- | Text t -> Text.to_json t
212212- | Tool_use t -> Tool_use.to_json t
213213- | Tool_result t -> Tool_result.to_json t
214214- | Thinking t -> Thinking.to_json t
276276+let jsont : t Jsont.t =
277277+ let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
278278+279279+ let case_text = case_map "text" Text.jsont (fun v -> Text v) in
280280+ let case_tool_use = case_map "tool_use" Tool_use.jsont (fun v -> Tool_use v) in
281281+ let case_tool_result = case_map "tool_result" Tool_result.jsont (fun v -> Tool_result v) in
282282+ let case_thinking = case_map "thinking" Thinking.jsont (fun v -> Thinking v) in
283283+284284+ let enc_case = function
285285+ | Text v -> Jsont.Object.Case.value case_text v
286286+ | Tool_use v -> Jsont.Object.Case.value case_tool_use v
287287+ | Tool_result v -> Jsont.Object.Case.value case_tool_result v
288288+ | Thinking v -> Jsont.Object.Case.value case_thinking v
289289+ in
290290+291291+ let cases = Jsont.Object.Case.[
292292+ make case_text;
293293+ make case_tool_use;
294294+ make case_tool_result;
295295+ make case_thinking
296296+ ] in
297297+298298+ Jsont.Object.map ~kind:"Content_block" Fun.id
299299+ |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
300300+ ~tag_to_string:Fun.id ~tag_compare:String.compare
301301+ |> Jsont.Object.finish
302302+303303+let to_json t =
304304+ match Jsont.Json.encode jsont t with
305305+ | Ok json -> json
306306+ | Error msg -> failwith ("Content_block.to_json: " ^ msg)
215307216308let of_json json =
217217- match json with
218218- | `O fields -> (
219219- match List.assoc_opt "type" fields with
220220- | Some (`String "text") -> Text (Text.of_json json)
221221- | Some (`String "tool_use") -> Tool_use (Tool_use.of_json json)
222222- | Some (`String "tool_result") -> Tool_result (Tool_result.of_json json)
223223- | Some (`String "thinking") -> Thinking (Thinking.of_json json)
224224- | _ -> raise (Invalid_argument "Content_block.of_json: unknown type")
225225- )
226226- | _ -> raise (Invalid_argument "Content_block.of_json: expected object")
309309+ match Jsont.Json.decode jsont json with
310310+ | Ok v -> v
311311+ | Error msg -> raise (Invalid_argument ("Content_block.of_json: " ^ msg))
227312228313let pp fmt = function
229314 | Text t -> Text.pp fmt t
+102-56
claudeio/lib/content_block.mli
···11(** Content blocks for Claude messages.
22-22+33 This module defines the various types of content blocks that can appear
44 in Claude messages, including text, tool use, tool results, and thinking blocks. *)
55···10101111module Text : sig
1212 (** Plain text content blocks. *)
1313-1313+1414+ module Unknown : sig
1515+ type t = Jsont.json
1616+ val empty : t
1717+ val is_empty : t -> bool
1818+ val jsont : t Jsont.t
1919+ end
2020+1421 type t
1522 (** The type of text blocks. *)
1616-2323+1724 val create : string -> t
1825 (** [create text] creates a new text block with the given text content. *)
1919-2626+2027 val text : t -> string
2128 (** [text t] returns the text content of the block. *)
2222-2323- val to_json : t -> Ezjsonm.value
2929+3030+ val jsont : t Jsont.t
3131+ (** [jsont] is the Jsont codec for text blocks. *)
3232+3333+ val to_json : t -> Jsont.json
2434 (** [to_json t] converts the text block to its JSON representation. *)
2525-2626- val of_json : Ezjsonm.value -> t
2727- (** [of_json json] parses a text block from JSON.
3535+3636+ val of_json : Jsont.json -> t
3737+ (** [of_json json] parses a text block from JSON.
2838 @raise Invalid_argument if the JSON is not a valid text block. *)
2929-3939+3040 val pp : Format.formatter -> t -> unit
3141 (** [pp fmt t] pretty-prints the text block. *)
3242end
···35453646module Tool_use : sig
3747 (** Tool invocation requests from the assistant. *)
3838-4848+3949 module Input : sig
4050 (** Tool input parameters. *)
4141-5151+4252 type t
4343- (** Abstract type for tool inputs. *)
4444-5353+ (** Abstract type for tool inputs (opaque JSON). *)
5454+5555+ val jsont : t Jsont.t
5656+ (** [jsont] is the Jsont codec for tool inputs. *)
5757+4558 val of_string_pairs : (string * string) list -> t
4659 (** [of_string_pairs pairs] creates tool input from string key-value pairs. *)
4747-4848- val of_assoc : (string * Ezjsonm.value) list -> t
6060+6161+ val of_assoc : (string * Jsont.json) list -> t
4962 (** [of_assoc assoc] creates tool input from an association list. *)
5050-6363+5164 val get_string : t -> string -> string option
5265 (** [get_string t key] returns the string value for [key], if present. *)
5353-6666+5467 val get_int : t -> string -> int option
5568 (** [get_int t key] returns the integer value for [key], if present. *)
5656-6969+5770 val get_bool : t -> string -> bool option
5871 (** [get_bool t key] returns the boolean value for [key], if present. *)
5959-7272+6073 val get_float : t -> string -> float option
6174 (** [get_float t key] returns the float value for [key], if present. *)
6262-7575+6376 val keys : t -> string list
6477 (** [keys t] returns all keys in the input. *)
6565-6666- val to_json : t -> Ezjsonm.value
7878+7979+ val to_json : t -> Jsont.json
6780 (** [to_json t] converts to JSON representation. Internal use only. *)
6868-6969- val of_json : Ezjsonm.value -> t
8181+8282+ val of_json : Jsont.json -> t
7083 (** [of_json json] parses from JSON. Internal use only. *)
7184 end
7272-8585+8686+ module Unknown : sig
8787+ type t = Jsont.json
8888+ val empty : t
8989+ val is_empty : t -> bool
9090+ val jsont : t Jsont.t
9191+ end
9292+7393 type t
7494 (** The type of tool use blocks. *)
7575-9595+7696 val create : id:string -> name:string -> input:Input.t -> t
7797 (** [create ~id ~name ~input] creates a new tool use block.
7898 @param id Unique identifier for this tool invocation
7999 @param name Name of the tool to invoke
80100 @param input Parameters for the tool *)
8181-101101+82102 val id : t -> string
83103 (** [id t] returns the unique identifier of the tool use. *)
8484-104104+85105 val name : t -> string
86106 (** [name t] returns the name of the tool being invoked. *)
8787-107107+88108 val input : t -> Input.t
89109 (** [input t] returns the input parameters for the tool. *)
9090-9191- val to_json : t -> Ezjsonm.value
110110+111111+ val jsont : t Jsont.t
112112+ (** [jsont] is the Jsont codec for tool use blocks. *)
113113+114114+ val to_json : t -> Jsont.json
92115 (** [to_json t] converts the tool use block to its JSON representation. *)
9393-9494- val of_json : Ezjsonm.value -> t
116116+117117+ val of_json : Jsont.json -> t
95118 (** [of_json json] parses a tool use block from JSON.
96119 @raise Invalid_argument if the JSON is not a valid tool use block. *)
9797-120120+98121 val pp : Format.formatter -> t -> unit
99122 (** [pp fmt t] pretty-prints the tool use block. *)
100123end
···103126104127module Tool_result : sig
105128 (** Results from tool invocations. *)
106106-129129+130130+ module Unknown : sig
131131+ type t = Jsont.json
132132+ val empty : t
133133+ val is_empty : t -> bool
134134+ val jsont : t Jsont.t
135135+ end
136136+107137 type t
108138 (** The type of tool result blocks. *)
109109-139139+110140 val create : tool_use_id:string -> ?content:string -> ?is_error:bool -> unit -> t
111141 (** [create ~tool_use_id ?content ?is_error ()] creates a new tool result block.
112142 @param tool_use_id The ID of the corresponding tool use block
113143 @param content Optional result content
114144 @param is_error Whether the tool execution resulted in an error *)
115115-145145+116146 val tool_use_id : t -> string
117147 (** [tool_use_id t] returns the ID of the corresponding tool use. *)
118118-148148+119149 val content : t -> string option
120150 (** [content t] returns the optional result content. *)
121121-151151+122152 val is_error : t -> bool option
123153 (** [is_error t] returns whether this result represents an error. *)
124124-125125- val to_json : t -> Ezjsonm.value
154154+155155+ val jsont : t Jsont.t
156156+ (** [jsont] is the Jsont codec for tool result blocks. *)
157157+158158+ val to_json : t -> Jsont.json
126159 (** [to_json t] converts the tool result block to its JSON representation. *)
127127-128128- val of_json : Ezjsonm.value -> t
160160+161161+ val of_json : Jsont.json -> t
129162 (** [of_json json] parses a tool result block from JSON.
130163 @raise Invalid_argument if the JSON is not a valid tool result block. *)
131131-164164+132165 val pp : Format.formatter -> t -> unit
133166 (** [pp fmt t] pretty-prints the tool result block. *)
134167end
···137170138171module Thinking : sig
139172 (** Assistant's internal reasoning blocks. *)
140140-173173+174174+ module Unknown : sig
175175+ type t = Jsont.json
176176+ val empty : t
177177+ val is_empty : t -> bool
178178+ val jsont : t Jsont.t
179179+ end
180180+141181 type t
142182 (** The type of thinking blocks. *)
143143-183183+144184 val create : thinking:string -> signature:string -> t
145185 (** [create ~thinking ~signature] creates a new thinking block.
146186 @param thinking The assistant's internal reasoning
147187 @param signature Cryptographic signature for verification *)
148148-188188+149189 val thinking : t -> string
150190 (** [thinking t] returns the thinking content. *)
151151-191191+152192 val signature : t -> string
153193 (** [signature t] returns the cryptographic signature. *)
154154-155155- val to_json : t -> Ezjsonm.value
194194+195195+ val jsont : t Jsont.t
196196+ (** [jsont] is the Jsont codec for thinking blocks. *)
197197+198198+ val to_json : t -> Jsont.json
156199 (** [to_json t] converts the thinking block to its JSON representation. *)
157157-158158- val of_json : Ezjsonm.value -> t
200200+201201+ val of_json : Jsont.json -> t
159202 (** [of_json json] parses a thinking block from JSON.
160203 @raise Invalid_argument if the JSON is not a valid thinking block. *)
161161-204204+162205 val pp : Format.formatter -> t -> unit
163206 (** [pp fmt t] pretty-prints the thinking block. *)
164207end
···184227val thinking : thinking:string -> signature:string -> t
185228(** [thinking ~thinking ~signature] creates a thinking content block. *)
186229187187-val to_json : t -> Ezjsonm.value
230230+val jsont : t Jsont.t
231231+(** [jsont] is the Jsont codec for content blocks. *)
232232+233233+val to_json : t -> Jsont.json
188234(** [to_json t] converts any content block to its JSON representation. *)
189235190190-val of_json : Ezjsonm.value -> t
236236+val of_json : Jsont.json -> t
191237(** [of_json json] parses a content block from JSON.
192238 @raise Invalid_argument if the JSON is not a valid content block. *)
193239
+38-18
claudeio/lib/control.ml
···11-open Ezjsonm
22-31let src = Logs.Src.create "claude.control" ~doc:"Claude control messages"
42module Log = (val Logs.src_log src : Logs.LOG)
5364(* Helper for pretty-printing JSON *)
75let pp_json fmt json =
88- Fmt.string fmt (value_to_string json)
66+ let s = match Jsont_bytesrw.encode_string' Jsont.json json with
77+ | Ok s -> s
88+ | Error err -> Jsont.Error.to_string err
99+ in
1010+ Fmt.string fmt s
1111+1212+module Unknown = struct
1313+ type t = Jsont.json
1414+ let empty = Jsont.Object ([], Jsont.Meta.none)
1515+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
1616+ let jsont = Jsont.json
1717+end
9181019type t = {
1120 request_id : string;
1221 subtype : string;
1313- data : value;
2222+ data : Jsont.json;
2323+ unknown : Unknown.t;
1424}
15251616-let create ~request_id ~subtype ~data = { request_id; subtype; data }
2626+let jsont =
2727+ Jsont.Object.map ~kind:"Control"
2828+ (fun request_id subtype data unknown -> {request_id; subtype; data; unknown})
2929+ |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun t -> t.request_id)
3030+ |> Jsont.Object.mem "subtype" Jsont.string ~enc:(fun t -> t.subtype)
3131+ |> Jsont.Object.mem "data" Jsont.json ~enc:(fun t -> t.data)
3232+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun t -> t.unknown)
3333+ |> Jsont.Object.finish
3434+3535+let create ~request_id ~subtype ~data =
3636+ { request_id; subtype; data; unknown = Unknown.empty }
17371838let request_id t = t.request_id
1939let subtype t = t.subtype
2040let data t = t.data
21412242let to_json t =
2323- `O [
2424- ("type", `String "control");
2525- ("request_id", `String t.request_id);
2626- ("subtype", `String t.subtype);
2727- ("data", t.data);
2828- ]
4343+ match Jsont_bytesrw.encode_string ~format:Jsont.Minify jsont t with
4444+ | Ok s ->
4545+ (match Jsont_bytesrw.decode_string' Jsont.json s with
4646+ | Ok json -> json
4747+ | Error e -> failwith (Jsont.Error.to_string e))
4848+ | Error e -> failwith e
29493030-let of_json = function
3131- | `O fields ->
3232- let request_id = get_string (List.assoc "request_id" fields) in
3333- let subtype = get_string (List.assoc "subtype" fields) in
3434- let data = List.assoc "data" fields in
3535- { request_id; subtype; data }
3636- | _ -> raise (Invalid_argument "Control.of_json: expected object")
5050+let of_json json =
5151+ match Jsont_bytesrw.encode_string ~format:Jsont.Minify Jsont.json json with
5252+ | Ok s ->
5353+ (match Jsont_bytesrw.decode_string jsont s with
5454+ | Ok t -> t
5555+ | Error e -> raise (Invalid_argument ("Control.of_json: " ^ e)))
5656+ | Error e -> raise (Invalid_argument ("Control.of_json: " ^ e))
37573858let pp fmt t =
3959 Fmt.pf fmt "@[<2>Control@ { request_id = %S;@ subtype = %S;@ data = %a }@]"
+16-7
claudeio/lib/control.mli
···11(** Control messages for Claude session management.
22-22+33 Control messages are used to manage the interaction flow with Claude,
44 including session control, cancellation requests, and other operational
55 commands. *)
6677-open Ezjsonm
88-97(** The log source for control message operations *)
108val src : Logs.Src.t
99+1010+(** Unknown field preservation *)
1111+module Unknown : sig
1212+ type t = Jsont.json
1313+ val empty : t
1414+ val is_empty : t -> bool
1515+ val jsont : t Jsont.t
1616+end
11171218type t
1319(** The type of control messages. *)
14201515-val create : request_id:string -> subtype:string -> data:value -> t
2121+val jsont : t Jsont.t
2222+(** [jsont] is the jsont codec for control messages. *)
2323+2424+val create : request_id:string -> subtype:string -> data:Jsont.json -> t
1625(** [create ~request_id ~subtype ~data] creates a new control message.
1726 @param request_id Unique identifier for this control request
1827 @param subtype The specific type of control message
···2433val subtype : t -> string
2534(** [subtype t] returns the control message subtype. *)
26352727-val data : t -> value
3636+val data : t -> Jsont.json
2837(** [data t] returns the additional data associated with the control message. *)
29383030-val to_json : t -> value
3939+val to_json : t -> Jsont.json
3140(** [to_json t] converts the control message to its JSON representation. *)
32413333-val of_json : value -> t
4242+val of_json : Jsont.json -> t
3443(** [of_json json] parses a control message from JSON.
3544 @raise Invalid_argument if the JSON is not a valid control message. *)
3645
···11-open Ezjsonm
22-31let src = Logs.Src.create "claude.hooks" ~doc:"Claude hooks system"
42module Log = (val Logs.src_log src : Logs.LOG)
53···2927 | "PreCompact" -> Pre_compact
3028 | s -> raise (Invalid_argument (Printf.sprintf "Unknown hook event: %s" s))
31293030+let event_jsont : event Jsont.t =
3131+ Jsont.enum [
3232+ "PreToolUse", Pre_tool_use;
3333+ "PostToolUse", Post_tool_use;
3434+ "UserPromptSubmit", User_prompt_submit;
3535+ "Stop", Stop;
3636+ "SubagentStop", Subagent_stop;
3737+ "PreCompact", Pre_compact;
3838+ ]
3939+3240(** Context provided to hook callbacks *)
3341module Context = struct
4242+ module Unknown = struct
4343+ type t = Jsont.json
4444+ let empty = Jsont.Object ([], Jsont.Meta.none)
4545+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
4646+ let jsont = Jsont.json
4747+ end
4848+3449 type t = {
3550 signal: unit option; (* Future: abort signal support *)
5151+ unknown : Unknown.t;
3652 }
37533838- let create ?(signal = None) () = { signal }
5454+ let create ?(signal = None) ?(unknown = Unknown.empty) () = { signal; unknown }
5555+5656+ let signal t = t.signal
5757+ let unknown t = t.unknown
5858+5959+ let jsont : t Jsont.t =
6060+ let make unknown = { signal = None; unknown } in
6161+ Jsont.Object.map ~kind:"Context" make
6262+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
6363+ |> Jsont.Object.finish
3964end
40654166(** Hook decision control *)
···4368 | Continue
4469 | Block
45707171+let decision_jsont : decision Jsont.t =
7272+ Jsont.enum [
7373+ "continue", Continue;
7474+ "block", Block;
7575+ ]
7676+4677(** Generic hook result *)
7878+module Result_unknown = struct
7979+ type t = Jsont.json
8080+ let empty = Jsont.Object ([], Jsont.Meta.none)
8181+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
8282+ let jsont = Jsont.json
8383+end
8484+4785type result = {
4886 decision: decision option;
4987 system_message: string option;
5050- hook_specific_output: value option;
8888+ hook_specific_output: Jsont.json option;
8989+ unknown : Result_unknown.t;
5190}
52919292+let result_jsont : result Jsont.t =
9393+ let make decision system_message hook_specific_output unknown =
9494+ { decision; system_message; hook_specific_output; unknown }
9595+ in
9696+ Jsont.Object.map ~kind:"Result" make
9797+ |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun r -> r.decision)
9898+ |> Jsont.Object.opt_mem "systemMessage" Jsont.string ~enc:(fun r -> r.system_message)
9999+ |> Jsont.Object.opt_mem "hookSpecificOutput" Jsont.json ~enc:(fun r -> r.hook_specific_output)
100100+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
101101+ |> Jsont.Object.finish
102102+53103(** {1 PreToolUse Hook} *)
54104module PreToolUse = struct
5555- type t = {
105105+ module Input_unknown = struct
106106+ type t = Jsont.json
107107+ let empty = Jsont.Object ([], Jsont.Meta.none)
108108+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
109109+ let jsont = Jsont.json
110110+ end
111111+112112+ type input = {
56113 session_id: string;
57114 transcript_path: string;
58115 tool_name: string;
5959- tool_input: value;
116116+ tool_input: Jsont.json;
117117+ unknown : Input_unknown.t;
60118 }
611196262- type permission_decision = Allow | Deny | Ask
120120+ let get_field json key =
121121+ match json with
122122+ | Jsont.Object (members, _) ->
123123+ List.find_map (fun ((name, _), value) ->
124124+ if name = key then Some value else None
125125+ ) members
126126+ | _ -> None
631276464- type output = {
6565- permission_decision: permission_decision option;
6666- permission_decision_reason: string option;
6767- }
128128+ let get_string json key =
129129+ match get_field json key with
130130+ | Some (Jsont.String (s, _)) -> Some s
131131+ | _ -> None
6813269133 let of_json json =
7070- {
7171- session_id = get_string (find json ["session_id"]);
7272- transcript_path = get_string (find json ["transcript_path"]);
7373- tool_name = get_string (find json ["tool_name"]);
7474- tool_input = find json ["tool_input"];
7575- }
134134+ match get_string json "session_id" with
135135+ | None -> raise (Invalid_argument "PreToolUse: missing session_id")
136136+ | Some session_id ->
137137+ match get_string json "transcript_path" with
138138+ | None -> raise (Invalid_argument "PreToolUse: missing transcript_path")
139139+ | Some transcript_path ->
140140+ match get_string json "tool_name" with
141141+ | None -> raise (Invalid_argument "PreToolUse: missing tool_name")
142142+ | Some tool_name ->
143143+ match get_field json "tool_input" with
144144+ | None -> raise (Invalid_argument "PreToolUse: missing tool_input")
145145+ | Some tool_input ->
146146+ { session_id; transcript_path; tool_name; tool_input; unknown = json }
147147+148148+ type t = input
7614977150 let session_id t = t.session_id
78151 let transcript_path t = t.transcript_path
79152 let tool_name t = t.tool_name
80153 let tool_input t = t.tool_input
8181- let raw_json t =
8282- dict [
8383- "session_id", string t.session_id;
8484- "transcript_path", string t.transcript_path;
8585- "hook_event_name", string "PreToolUse";
8686- "tool_name", string t.tool_name;
8787- "tool_input", t.tool_input;
154154+ let unknown t = t.unknown
155155+156156+ let input_jsont : input Jsont.t =
157157+ let make session_id transcript_path tool_name tool_input unknown =
158158+ { session_id; transcript_path; tool_name; tool_input; unknown }
159159+ in
160160+ Jsont.Object.map ~kind:"PreToolUseInput" make
161161+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
162162+ |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
163163+ |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
164164+ |> Jsont.Object.mem "tool_input" Jsont.json ~enc:tool_input
165165+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
166166+ |> Jsont.Object.finish
167167+168168+ type permission_decision = [ `Allow | `Deny | `Ask ]
169169+170170+ let permission_decision_jsont : permission_decision Jsont.t =
171171+ Jsont.enum [
172172+ "allow", `Allow;
173173+ "deny", `Deny;
174174+ "ask", `Ask;
88175 ]
891769090- let permission_decision_to_string = function
9191- | Allow -> "allow"
9292- | Deny -> "deny"
9393- | Ask -> "ask"
177177+ module Output_unknown = struct
178178+ type t = Jsont.json
179179+ let empty = Jsont.Object ([], Jsont.Meta.none)
180180+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
181181+ let jsont = Jsont.json
182182+ end
941839595- let output_to_json output =
9696- let fields = [("hookEventName", string "PreToolUse")] in
9797- let fields = match output.permission_decision with
9898- | Some pd -> ("permissionDecision", string (permission_decision_to_string pd)) :: fields
9999- | None -> fields
184184+ type output = {
185185+ permission_decision: permission_decision option;
186186+ permission_decision_reason: string option;
187187+ updated_input: Jsont.json option;
188188+ unknown : Output_unknown.t;
189189+ }
190190+191191+ let output_jsont : output Jsont.t =
192192+ let make permission_decision permission_decision_reason updated_input unknown =
193193+ { permission_decision; permission_decision_reason; updated_input; unknown }
100194 in
101101- let fields = match output.permission_decision_reason with
102102- | Some reason -> ("permissionDecisionReason", string reason) :: fields
103103- | None -> fields
104104- in
105105- dict fields
195195+ Jsont.Object.map ~kind:"PreToolUseOutput" make
196196+ |> Jsont.Object.opt_mem "permissionDecision" permission_decision_jsont ~enc:(fun o -> o.permission_decision)
197197+ |> Jsont.Object.opt_mem "permissionDecisionReason" Jsont.string ~enc:(fun o -> o.permission_decision_reason)
198198+ |> Jsont.Object.opt_mem "updatedInput" Jsont.json ~enc:(fun o -> o.updated_input)
199199+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun o -> o.unknown)
200200+ |> Jsont.Object.finish
106201107107- let allow ?reason () =
108108- { permission_decision = Some Allow; permission_decision_reason = reason }
202202+ let output_to_json output =
203203+ match Jsont.Json.encode output_jsont output with
204204+ | Ok json -> json
205205+ | Error msg -> failwith ("PreToolUse.output_to_json: " ^ msg)
109206110110- let deny ?reason () =
111111- { permission_decision = Some Deny; permission_decision_reason = reason }
207207+ let allow ?reason ?updated_input ?(unknown = Output_unknown.empty) () =
208208+ { permission_decision = Some `Allow; permission_decision_reason = reason;
209209+ updated_input; unknown }
112210113113- let ask ?reason () =
114114- { permission_decision = Some Ask; permission_decision_reason = reason }
211211+ let deny ?reason ?(unknown = Output_unknown.empty) () =
212212+ { permission_decision = Some `Deny; permission_decision_reason = reason;
213213+ updated_input = None; unknown }
214214+215215+ let ask ?reason ?(unknown = Output_unknown.empty) () =
216216+ { permission_decision = Some `Ask; permission_decision_reason = reason;
217217+ updated_input = None; unknown }
115218116116- let continue () =
117117- { permission_decision = None; permission_decision_reason = None }
219219+ let continue ?(unknown = Output_unknown.empty) () =
220220+ { permission_decision = None; permission_decision_reason = None;
221221+ updated_input = None; unknown }
118222end
119223120224(** {1 PostToolUse Hook} *)
121225module PostToolUse = struct
122122- type t = {
226226+ module Input_unknown = struct
227227+ type t = Jsont.json
228228+ let empty = Jsont.Object ([], Jsont.Meta.none)
229229+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
230230+ let jsont = Jsont.json
231231+ end
232232+233233+ type input = {
123234 session_id: string;
124235 transcript_path: string;
125236 tool_name: string;
126126- tool_input: value;
127127- tool_response: value;
237237+ tool_input: Jsont.json;
238238+ tool_response: Jsont.json;
239239+ unknown : Input_unknown.t;
128240 }
129241130130- type output = {
131131- decision: decision option;
132132- reason: string option;
133133- additional_context: string option;
134134- }
242242+ let get_field json key =
243243+ match json with
244244+ | Jsont.Object (members, _) ->
245245+ List.find_map (fun ((name, _), value) ->
246246+ if name = key then Some value else None
247247+ ) members
248248+ | _ -> None
249249+250250+ let get_string json key =
251251+ match get_field json key with
252252+ | Some (Jsont.String (s, _)) -> Some s
253253+ | _ -> None
135254136255 let of_json json =
137137- {
138138- session_id = get_string (find json ["session_id"]);
139139- transcript_path = get_string (find json ["transcript_path"]);
140140- tool_name = get_string (find json ["tool_name"]);
141141- tool_input = find json ["tool_input"];
142142- tool_response = find json ["tool_response"];
143143- }
256256+ match get_string json "session_id" with
257257+ | None -> raise (Invalid_argument "PostToolUse: missing session_id")
258258+ | Some session_id ->
259259+ match get_string json "transcript_path" with
260260+ | None -> raise (Invalid_argument "PostToolUse: missing transcript_path")
261261+ | Some transcript_path ->
262262+ match get_string json "tool_name" with
263263+ | None -> raise (Invalid_argument "PostToolUse: missing tool_name")
264264+ | Some tool_name ->
265265+ match get_field json "tool_input" with
266266+ | None -> raise (Invalid_argument "PostToolUse: missing tool_input")
267267+ | Some tool_input ->
268268+ match get_field json "tool_response" with
269269+ | None -> raise (Invalid_argument "PostToolUse: missing tool_response")
270270+ | Some tool_response ->
271271+ { session_id; transcript_path; tool_name; tool_input; tool_response; unknown = json }
272272+273273+ type t = input
144274145275 let session_id t = t.session_id
146276 let transcript_path t = t.transcript_path
147277 let tool_name t = t.tool_name
148278 let tool_input t = t.tool_input
149279 let tool_response t = t.tool_response
150150- let raw_json t =
151151- dict [
152152- "session_id", string t.session_id;
153153- "transcript_path", string t.transcript_path;
154154- "hook_event_name", string "PostToolUse";
155155- "tool_name", string t.tool_name;
156156- "tool_input", t.tool_input;
157157- "tool_response", t.tool_response;
158158- ]
280280+ let unknown t = t.unknown
159281160160- let output_to_json output =
161161- let fields = [("hookEventName", string "PostToolUse")] in
162162- let fields = match output.decision with
163163- | Some Block -> ("decision", string "block") :: fields
164164- | Some Continue | None -> fields
282282+ let input_jsont : input Jsont.t =
283283+ let make session_id transcript_path tool_name tool_input tool_response unknown =
284284+ { session_id; transcript_path; tool_name; tool_input; tool_response; unknown }
165285 in
166166- let fields = match output.reason with
167167- | Some r -> ("reason", string r) :: fields
168168- | None -> fields
286286+ Jsont.Object.map ~kind:"PostToolUseInput" 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 "tool_name" Jsont.string ~enc:tool_name
290290+ |> Jsont.Object.mem "tool_input" Jsont.json ~enc:tool_input
291291+ |> Jsont.Object.mem "tool_response" Jsont.json ~enc:tool_response
292292+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
293293+ |> Jsont.Object.finish
294294+295295+ module Output_unknown = struct
296296+ type t = Jsont.json
297297+ let empty = Jsont.Object ([], Jsont.Meta.none)
298298+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
299299+ let jsont = Jsont.json
300300+ end
301301+302302+ type output = {
303303+ decision: decision option;
304304+ reason: string option;
305305+ additional_context: string option;
306306+ unknown : Output_unknown.t;
307307+ }
308308+309309+ let output_jsont : output Jsont.t =
310310+ let make decision reason additional_context unknown =
311311+ { decision; reason; additional_context; unknown }
169312 in
170170- let fields = match output.additional_context with
171171- | Some ctx -> ("additionalContext", string ctx) :: fields
172172- | None -> fields
173173- in
174174- dict fields
313313+ Jsont.Object.map ~kind:"PostToolUseOutput" make
314314+ |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o -> o.decision)
315315+ |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
316316+ |> Jsont.Object.opt_mem "additionalContext" Jsont.string ~enc:(fun o -> o.additional_context)
317317+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun o -> o.unknown)
318318+ |> Jsont.Object.finish
175319176176- let continue ?additional_context () =
177177- { decision = None; reason = None; additional_context }
320320+ let output_to_json output =
321321+ match Jsont.Json.encode output_jsont output with
322322+ | Ok json -> json
323323+ | Error msg -> failwith ("PostToolUse.output_to_json: " ^ msg)
178324179179- let block ?reason ?additional_context () =
180180- { decision = Some Block; reason; additional_context }
325325+ let continue ?additional_context ?(unknown = Output_unknown.empty) () =
326326+ { decision = None; reason = None; additional_context; unknown }
327327+328328+ let block ?reason ?additional_context ?(unknown = Output_unknown.empty) () =
329329+ { decision = Some Block; reason; additional_context; unknown }
181330end
182331183332(** {1 UserPromptSubmit Hook} *)
184333module UserPromptSubmit = struct
185185- type t = {
334334+ module Input_unknown = struct
335335+ type t = Jsont.json
336336+ let empty = Jsont.Object ([], Jsont.Meta.none)
337337+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
338338+ let jsont = Jsont.json
339339+ end
340340+341341+ type input = {
186342 session_id: string;
187343 transcript_path: string;
188344 prompt: string;
345345+ unknown : Input_unknown.t;
189346 }
190347191191- type output = {
192192- decision: decision option;
193193- reason: string option;
194194- additional_context: string option;
195195- }
348348+ let get_field json key =
349349+ match json with
350350+ | Jsont.Object (members, _) ->
351351+ List.find_map (fun ((name, _), value) ->
352352+ if name = key then Some value else None
353353+ ) members
354354+ | _ -> None
355355+356356+ let get_string json key =
357357+ match get_field json key with
358358+ | Some (Jsont.String (s, _)) -> Some s
359359+ | _ -> None
196360197361 let of_json json =
198198- {
199199- session_id = get_string (find json ["session_id"]);
200200- transcript_path = get_string (find json ["transcript_path"]);
201201- prompt = get_string (find json ["prompt"]);
202202- }
362362+ match get_string json "session_id" with
363363+ | None -> raise (Invalid_argument "UserPromptSubmit: missing session_id")
364364+ | Some session_id ->
365365+ match get_string json "transcript_path" with
366366+ | None -> raise (Invalid_argument "UserPromptSubmit: missing transcript_path")
367367+ | Some transcript_path ->
368368+ match get_string json "prompt" with
369369+ | None -> raise (Invalid_argument "UserPromptSubmit: missing prompt")
370370+ | Some prompt ->
371371+ { session_id; transcript_path; prompt; unknown = json }
372372+373373+ type t = input
203374204375 let session_id t = t.session_id
205376 let transcript_path t = t.transcript_path
206377 let prompt t = t.prompt
207207- let raw_json t =
208208- dict [
209209- "session_id", string t.session_id;
210210- "transcript_path", string t.transcript_path;
211211- "hook_event_name", string "UserPromptSubmit";
212212- "prompt", string t.prompt;
213213- ]
378378+ let unknown t = t.unknown
214379215215- let output_to_json output =
216216- let fields = [("hookEventName", string "UserPromptSubmit")] in
217217- let fields = match output.decision with
218218- | Some Block -> ("decision", string "block") :: fields
219219- | Some Continue | None -> fields
380380+ let input_jsont : input Jsont.t =
381381+ let make session_id transcript_path prompt unknown =
382382+ { session_id; transcript_path; prompt; unknown }
220383 in
221221- let fields = match output.reason with
222222- | Some r -> ("reason", string r) :: fields
223223- | None -> fields
224224- in
225225- let fields = match output.additional_context with
226226- | Some ctx -> ("additionalContext", string ctx) :: fields
227227- | None -> fields
384384+ Jsont.Object.map ~kind:"UserPromptSubmitInput" make
385385+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
386386+ |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
387387+ |> Jsont.Object.mem "prompt" Jsont.string ~enc:prompt
388388+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
389389+ |> Jsont.Object.finish
390390+391391+ module Output_unknown = struct
392392+ type t = Jsont.json
393393+ let empty = Jsont.Object ([], Jsont.Meta.none)
394394+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
395395+ let jsont = Jsont.json
396396+ end
397397+398398+ type output = {
399399+ decision: decision option;
400400+ reason: string option;
401401+ additional_context: string option;
402402+ unknown : Output_unknown.t;
403403+ }
404404+405405+ let output_jsont : output Jsont.t =
406406+ let make decision reason additional_context unknown =
407407+ { decision; reason; additional_context; unknown }
228408 in
229229- dict fields
409409+ Jsont.Object.map ~kind:"UserPromptSubmitOutput" make
410410+ |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o -> o.decision)
411411+ |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
412412+ |> Jsont.Object.opt_mem "additionalContext" Jsont.string ~enc:(fun o -> o.additional_context)
413413+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun o -> o.unknown)
414414+ |> Jsont.Object.finish
230415231231- let continue ?additional_context () =
232232- { decision = None; reason = None; additional_context }
416416+ let output_to_json output =
417417+ match Jsont.Json.encode output_jsont output with
418418+ | Ok json -> json
419419+ | Error msg -> failwith ("UserPromptSubmit.output_to_json: " ^ msg)
233420234234- let block ?reason () =
235235- { decision = Some Block; reason; additional_context = None }
421421+ let continue ?additional_context ?(unknown = Output_unknown.empty) () =
422422+ { decision = None; reason = None; additional_context; unknown }
423423+424424+ let block ?reason ?(unknown = Output_unknown.empty) () =
425425+ { decision = Some Block; reason; additional_context = None; unknown }
236426end
237427238428(** {1 Stop Hook} *)
239429module Stop = struct
240240- type t = {
430430+ module Input_unknown = struct
431431+ type t = Jsont.json
432432+ let empty = Jsont.Object ([], Jsont.Meta.none)
433433+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
434434+ let jsont = Jsont.json
435435+ end
436436+437437+ type input = {
241438 session_id: string;
242439 transcript_path: string;
243440 stop_hook_active: bool;
441441+ unknown : Input_unknown.t;
244442 }
245443246246- type output = {
247247- decision: decision option;
248248- reason: string option;
249249- }
444444+ let get_field json key =
445445+ match json with
446446+ | Jsont.Object (members, _) ->
447447+ List.find_map (fun ((name, _), value) ->
448448+ if name = key then Some value else None
449449+ ) members
450450+ | _ -> None
451451+452452+ let get_string json key =
453453+ match get_field json key with
454454+ | Some (Jsont.String (s, _)) -> Some s
455455+ | _ -> None
456456+457457+ let get_bool json key =
458458+ match get_field json key with
459459+ | Some (Jsont.Bool (b, _)) -> Some b
460460+ | _ -> None
250461251462 let of_json json =
252252- {
253253- session_id = get_string (find json ["session_id"]);
254254- transcript_path = get_string (find json ["transcript_path"]);
255255- stop_hook_active = get_bool (find json ["stop_hook_active"]);
256256- }
463463+ match get_string json "session_id" with
464464+ | None -> raise (Invalid_argument "Stop: missing session_id")
465465+ | Some session_id ->
466466+ match get_string json "transcript_path" with
467467+ | None -> raise (Invalid_argument "Stop: missing transcript_path")
468468+ | Some transcript_path ->
469469+ match get_bool json "stop_hook_active" with
470470+ | None -> raise (Invalid_argument "Stop: missing stop_hook_active")
471471+ | Some stop_hook_active ->
472472+ { session_id; transcript_path; stop_hook_active; unknown = json }
473473+474474+ type t = input
257475258476 let session_id t = t.session_id
259477 let transcript_path t = t.transcript_path
260478 let stop_hook_active t = t.stop_hook_active
261261- let raw_json t =
262262- dict [
263263- "session_id", string t.session_id;
264264- "transcript_path", string t.transcript_path;
265265- "hook_event_name", string "Stop";
266266- "stop_hook_active", bool t.stop_hook_active;
267267- ]
479479+ let unknown t = t.unknown
268480269269- let output_to_json output =
270270- let fields = [] in
271271- let fields = match output.decision with
272272- | Some Block -> ("decision", string "block") :: fields
273273- | Some Continue | None -> fields
481481+ let input_jsont : input Jsont.t =
482482+ let make session_id transcript_path stop_hook_active unknown =
483483+ { session_id; transcript_path; stop_hook_active; unknown }
274484 in
275275- let fields = match output.reason with
276276- | Some r -> ("reason", string r) :: fields
277277- | None -> fields
485485+ Jsont.Object.map ~kind:"StopInput" make
486486+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
487487+ |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
488488+ |> Jsont.Object.mem "stop_hook_active" Jsont.bool ~enc:stop_hook_active
489489+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
490490+ |> Jsont.Object.finish
491491+492492+ module Output_unknown = struct
493493+ type t = Jsont.json
494494+ let empty = Jsont.Object ([], Jsont.Meta.none)
495495+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
496496+ let jsont = Jsont.json
497497+ end
498498+499499+ type output = {
500500+ decision: decision option;
501501+ reason: string option;
502502+ unknown : Output_unknown.t;
503503+ }
504504+505505+ let output_jsont : output Jsont.t =
506506+ let make decision reason unknown =
507507+ { decision; reason; unknown }
278508 in
279279- dict fields
509509+ Jsont.Object.map ~kind:"StopOutput" make
510510+ |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o -> o.decision)
511511+ |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
512512+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun o -> o.unknown)
513513+ |> Jsont.Object.finish
514514+515515+ let output_to_json output =
516516+ match Jsont.Json.encode output_jsont output with
517517+ | Ok json -> json
518518+ | Error msg -> failwith ("Stop.output_to_json: " ^ msg)
280519281281- let continue () = { decision = None; reason = None }
282282- let block ?reason () = { decision = Some Block; reason }
520520+ let continue ?(unknown = Output_unknown.empty) () = { decision = None; reason = None; unknown }
521521+ let block ?reason ?(unknown = Output_unknown.empty) () = { decision = Some Block; reason; unknown }
283522end
284523285524(** {1 SubagentStop Hook} - Same structure as Stop *)
286525module SubagentStop = struct
287526 include Stop
288527528528+ let get_field json key =
529529+ match json with
530530+ | Jsont.Object (members, _) ->
531531+ List.find_map (fun ((name, _), value) ->
532532+ if name = key then Some value else None
533533+ ) members
534534+ | _ -> None
535535+536536+ let get_string json key =
537537+ match get_field json key with
538538+ | Some (Jsont.String (s, _)) -> Some s
539539+ | _ -> None
540540+541541+ let get_bool json key =
542542+ match get_field json key with
543543+ | Some (Jsont.Bool (b, _)) -> Some b
544544+ | _ -> None
545545+289546 let of_json json =
290290- {
291291- session_id = get_string (find json ["session_id"]);
292292- transcript_path = get_string (find json ["transcript_path"]);
293293- stop_hook_active = get_bool (find json ["stop_hook_active"]);
294294- }
295295-296296- let raw_json t =
297297- dict [
298298- "session_id", string t.session_id;
299299- "transcript_path", string t.transcript_path;
300300- "hook_event_name", string "SubagentStop";
301301- "stop_hook_active", bool t.stop_hook_active;
302302- ]
547547+ match get_string json "session_id" with
548548+ | None -> raise (Invalid_argument "SubagentStop: missing session_id")
549549+ | Some session_id ->
550550+ match get_string json "transcript_path" with
551551+ | None -> raise (Invalid_argument "SubagentStop: missing transcript_path")
552552+ | Some transcript_path ->
553553+ match get_bool json "stop_hook_active" with
554554+ | None -> raise (Invalid_argument "SubagentStop: missing stop_hook_active")
555555+ | Some stop_hook_active ->
556556+ { session_id; transcript_path; stop_hook_active; unknown = json }
303557end
304558305559(** {1 PreCompact Hook} *)
306560module PreCompact = struct
307307- type t = {
561561+ module Input_unknown = struct
562562+ type t = Jsont.json
563563+ let empty = Jsont.Object ([], Jsont.Meta.none)
564564+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
565565+ let jsont = Jsont.json
566566+ end
567567+568568+ type input = {
308569 session_id: string;
309570 transcript_path: string;
571571+ unknown : Input_unknown.t;
310572 }
311573312312- type output = unit (* No specific output for PreCompact *)
574574+ let get_field json key =
575575+ match json with
576576+ | Jsont.Object (members, _) ->
577577+ List.find_map (fun ((name, _), value) ->
578578+ if name = key then Some value else None
579579+ ) members
580580+ | _ -> None
581581+582582+ let get_string json key =
583583+ match get_field json key with
584584+ | Some (Jsont.String (s, _)) -> Some s
585585+ | _ -> None
313586314587 let of_json json =
315315- {
316316- session_id = get_string (find json ["session_id"]);
317317- transcript_path = get_string (find json ["transcript_path"]);
318318- }
588588+ match get_string json "session_id" with
589589+ | None -> raise (Invalid_argument "PreCompact: missing session_id")
590590+ | Some session_id ->
591591+ match get_string json "transcript_path" with
592592+ | None -> raise (Invalid_argument "PreCompact: missing transcript_path")
593593+ | Some transcript_path ->
594594+ { session_id; transcript_path; unknown = json }
595595+596596+ type t = input
319597320598 let session_id t = t.session_id
321599 let transcript_path t = t.transcript_path
322322- let raw_json t =
323323- dict [
324324- "session_id", string t.session_id;
325325- "transcript_path", string t.transcript_path;
326326- "hook_event_name", string "PreCompact";
327327- ]
600600+ let unknown t = t.unknown
601601+602602+ let input_jsont : input Jsont.t =
603603+ let make session_id transcript_path unknown =
604604+ { session_id; transcript_path; unknown }
605605+ in
606606+ Jsont.Object.map ~kind:"PreCompactInput" make
607607+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
608608+ |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
609609+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
610610+ |> Jsont.Object.finish
611611+612612+ type output = unit (* No specific output for PreCompact *)
328613329329- let output_to_json () = dict []
614614+ let output_to_json () = Jsont.Object ([], Jsont.Meta.none)
330615331616 let continue () = ()
332617end
333618334619(** {1 Generic Callback Type} *)
335620type callback =
336336- input:value ->
621621+ input:Jsont.json ->
337622 tool_use_id:string option ->
338623 context:Context.t ->
339624 result
···347632type config = (event * matcher list) list
348633349634(** {1 Result Builders} *)
350350-let continue ?system_message ?hook_specific_output () =
351351- { decision = None; system_message; hook_specific_output }
635635+let continue ?system_message ?hook_specific_output ?(unknown = Result_unknown.empty) () =
636636+ { decision = None; system_message; hook_specific_output; unknown }
352637353353-let block ?system_message ?hook_specific_output () =
354354- { decision = Some Block; system_message; hook_specific_output }
638638+let block ?system_message ?hook_specific_output ?(unknown = Result_unknown.empty) () =
639639+ { decision = Some Block; system_message; hook_specific_output; unknown }
355640356641(** {1 Matcher Builders} *)
357642let matcher ?pattern callbacks = { matcher = pattern; callbacks }
···364649365650(** {1 JSON Conversion} *)
366651let result_to_json result =
367367- let fields = [] in
368368- let fields = match result.decision with
369369- | Some Block -> ("decision", string "block") :: fields
370370- | Some Continue | None -> fields
371371- in
372372- let fields = match result.system_message with
373373- | Some msg -> ("systemMessage", string msg) :: fields
374374- | None -> fields
375375- in
376376- let fields = match result.hook_specific_output with
377377- | Some output -> ("hookSpecificOutput", output) :: fields
378378- | None -> fields
379379- in
380380- dict fields
652652+ match Jsont.Json.encode result_jsont result with
653653+ | Ok json -> json
654654+ | Error msg -> failwith ("result_to_json: " ^ msg)
381655382656let config_to_protocol_format config =
383657 let hooks_dict = List.map (fun (event, matchers) ->
384658 let event_name = event_to_string event in
385659 let matchers_json = List.map (fun m ->
386660 (* matcher and hookCallbackIds will be filled in by client *)
387387- dict [
388388- "matcher", (match m.matcher with Some p -> string p | None -> `Null);
389389- "callbacks", `A []; (* Placeholder, filled by client *)
390390- ]
661661+ let mems = [
662662+ Jsont.Json.mem (Jsont.Json.name "matcher") (match m.matcher with
663663+ | Some p -> Jsont.Json.string p
664664+ | None -> Jsont.Json.null ());
665665+ Jsont.Json.mem (Jsont.Json.name "callbacks") (Jsont.Json.list []); (* Placeholder, filled by client *)
666666+ ] in
667667+ Jsont.Json.object' mems
391668 ) matchers in
392392- (event_name, `A matchers_json)
669669+ Jsont.Json.mem (Jsont.Json.name event_name) (Jsont.Json.list matchers_json)
393670 ) config in
394394- dict hooks_dict
671671+ Jsont.Json.object' hooks_dict
+228-64
claudeio/lib/hooks.mli
···1616 open Eio.Std
17171818 (* 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
1931 let block_rm_rf ~input ~tool_use_id:_ ~context:_ =
2032 let hook = Hooks.PreToolUse.of_json input in
2133 if Hooks.PreToolUse.tool_name hook = "Bash" then
2234 let tool_input = Hooks.PreToolUse.tool_input hook in
2323- match Ezjsonm.find tool_input ["command"] with
2424- | `String cmd when String.contains cmd "rm -rf" ->
3535+ match get_string tool_input "command" with
3636+ | Some cmd when String.contains cmd "rm -rf" ->
2537 let output = Hooks.PreToolUse.deny ~reason:"Dangerous command" () in
2638 Hooks.continue
2739 ~hook_specific_output:(Hooks.PreToolUse.output_to_json output)
···56685769val event_to_string : event -> string
5870val event_of_string : string -> event
7171+val event_jsont : event Jsont.t
59726073(** {1 Context} *)
61746275module Context : sig
6363- type t
6464- val create : ?signal:unit option -> unit -> t
7676+ module Unknown : sig
7777+ type t = Jsont.json
7878+ val empty : t
7979+ val is_empty : t -> bool
8080+ val jsont : t Jsont.t
8181+ end
8282+8383+ type t = {
8484+ signal: unit option;
8585+ unknown : Unknown.t;
8686+ }
8787+8888+ val create : ?signal:unit option -> ?unknown:Unknown.t -> unit -> t
8989+ val signal : t -> unit option
9090+ val unknown : t -> Unknown.t
9191+ val jsont : t Jsont.t
6592end
66936794(** {1 Decisions} *)
···6996type decision =
7097 | Continue (** Allow the action to proceed *)
7198 | Block (** Block the action *)
9999+100100+val decision_jsont : decision Jsont.t
7210173102(** {1 Generic Hook Result} *)
74103104104+module Result_unknown : sig
105105+ type t = Jsont.json
106106+ val empty : t
107107+ val is_empty : t -> bool
108108+ val jsont : t Jsont.t
109109+end
110110+75111(** Generic result structure for hooks *)
76112type result = {
77113 decision: decision option;
78114 system_message: string option;
7979- hook_specific_output: Ezjsonm.value option;
115115+ hook_specific_output: Jsont.json option;
116116+ unknown: Result_unknown.t;
80117}
118118+119119+val result_jsont : result Jsont.t
8112082121(** {1 Typed Hook Modules} *)
8312284123(** PreToolUse hook - fires before tool execution *)
85124module PreToolUse : sig
125125+ module Input_unknown : sig
126126+ type t = Jsont.json
127127+ val empty : t
128128+ val is_empty : t -> bool
129129+ val jsont : t Jsont.t
130130+ end
131131+86132 (** Typed input for PreToolUse hooks *)
8787- type t
133133+ type input = {
134134+ session_id: string;
135135+ transcript_path: string;
136136+ tool_name: string;
137137+ tool_input: Jsont.json;
138138+ unknown: Input_unknown.t;
139139+ }
140140+141141+ type t = input
142142+143143+ (** Parse hook input from JSON *)
144144+ val of_json : Jsont.json -> t
145145+146146+ (** {2 Accessors} *)
147147+ val session_id : t -> string
148148+ val transcript_path : t -> string
149149+ val tool_name : t -> string
150150+ val tool_input : t -> Jsont.json
151151+ val unknown : t -> Input_unknown.t
152152+153153+ val input_jsont : input Jsont.t
8815489155 (** Permission decision for tool usage *)
9090- type permission_decision = Allow | Deny | Ask
156156+ type permission_decision = [ `Allow | `Deny | `Ask ]
157157+158158+ val permission_decision_jsont : permission_decision Jsont.t
159159+160160+ module Output_unknown : sig
161161+ type t = Jsont.json
162162+ val empty : t
163163+ val is_empty : t -> bool
164164+ val jsont : t Jsont.t
165165+ end
9116692167 (** Typed output for PreToolUse hooks *)
93168 type output = {
94169 permission_decision: permission_decision option;
95170 permission_decision_reason: string option;
171171+ updated_input: Jsont.json option;
172172+ unknown: Output_unknown.t;
96173 }
971749898- (** Parse hook input from JSON *)
9999- val of_json : Ezjsonm.value -> t
100100-101101- (** {2 Accessors} *)
102102- val session_id : t -> string
103103- val transcript_path : t -> string
104104- val tool_name : t -> string
105105- val tool_input : t -> Ezjsonm.value
106106- val raw_json : t -> Ezjsonm.value
175175+ val output_jsont : output Jsont.t
107176108177 (** {2 Response Builders} *)
109109- val allow : ?reason:string -> unit -> output
110110- val deny : ?reason:string -> unit -> output
111111- val ask : ?reason:string -> unit -> output
112112- val continue : unit -> output
178178+ val allow : ?reason:string -> ?updated_input:Jsont.json -> ?unknown:Output_unknown.t -> unit -> output
179179+ val deny : ?reason:string -> ?unknown:Output_unknown.t -> unit -> output
180180+ val ask : ?reason:string -> ?unknown:Output_unknown.t -> unit -> output
181181+ val continue : ?unknown:Output_unknown.t -> unit -> output
113182114183 (** Convert output to JSON for hook_specific_output *)
115115- val output_to_json : output -> Ezjsonm.value
184184+ val output_to_json : output -> Jsont.json
116185end
117186118187(** PostToolUse hook - fires after tool execution *)
119188module PostToolUse : sig
120120- type t
189189+ module Input_unknown : sig
190190+ type t = Jsont.json
191191+ val empty : t
192192+ val is_empty : t -> bool
193193+ val jsont : t Jsont.t
194194+ end
121195122122- type output = {
123123- decision: decision option;
124124- reason: string option;
125125- additional_context: string option;
196196+ type input = {
197197+ session_id: string;
198198+ transcript_path: string;
199199+ tool_name: string;
200200+ tool_input: Jsont.json;
201201+ tool_response: Jsont.json;
202202+ unknown: Input_unknown.t;
126203 }
127204128128- val of_json : Ezjsonm.value -> t
205205+ type t = input
206206+207207+ val of_json : Jsont.json -> t
129208130209 val session_id : t -> string
131210 val transcript_path : t -> string
132211 val tool_name : t -> string
133133- val tool_input : t -> Ezjsonm.value
134134- val tool_response : t -> Ezjsonm.value
135135- val raw_json : t -> Ezjsonm.value
212212+ val tool_input : t -> Jsont.json
213213+ val tool_response : t -> Jsont.json
214214+ val unknown : t -> Input_unknown.t
136215137137- val continue : ?additional_context:string -> unit -> output
138138- val block : ?reason:string -> ?additional_context:string -> unit -> output
139139- val output_to_json : output -> Ezjsonm.value
140140-end
216216+ val input_jsont : input Jsont.t
141217142142-(** UserPromptSubmit hook - fires when user submits a prompt *)
143143-module UserPromptSubmit : sig
144144- type t
218218+ module Output_unknown : sig
219219+ type t = Jsont.json
220220+ val empty : t
221221+ val is_empty : t -> bool
222222+ val jsont : t Jsont.t
223223+ end
145224146225 type output = {
147226 decision: decision option;
148227 reason: string option;
149228 additional_context: string option;
229229+ unknown: Output_unknown.t;
150230 }
151231152152- val of_json : Ezjsonm.value -> t
232232+ val output_jsont : output Jsont.t
233233+234234+ val continue : ?additional_context:string -> ?unknown:Output_unknown.t -> unit -> output
235235+ val block : ?reason:string -> ?additional_context:string -> ?unknown:Output_unknown.t -> unit -> output
236236+ val output_to_json : output -> Jsont.json
237237+end
238238+239239+(** UserPromptSubmit hook - fires when user submits a prompt *)
240240+module UserPromptSubmit : sig
241241+ module Input_unknown : sig
242242+ type t = Jsont.json
243243+ val empty : t
244244+ val is_empty : t -> bool
245245+ val jsont : t Jsont.t
246246+ end
247247+248248+ type input = {
249249+ session_id: string;
250250+ transcript_path: string;
251251+ prompt: string;
252252+ unknown: Input_unknown.t;
253253+ }
254254+255255+ type t = input
256256+257257+ val of_json : Jsont.json -> t
153258154259 val session_id : t -> string
155260 val transcript_path : t -> string
156261 val prompt : t -> string
157157- val raw_json : t -> Ezjsonm.value
262262+ val unknown : t -> Input_unknown.t
158263159159- val continue : ?additional_context:string -> unit -> output
160160- val block : ?reason:string -> unit -> output
161161- val output_to_json : output -> Ezjsonm.value
162162-end
264264+ val input_jsont : input Jsont.t
163265164164-(** Stop hook - fires when conversation stops *)
165165-module Stop : sig
166166- type t
266266+ module Output_unknown : sig
267267+ type t = Jsont.json
268268+ val empty : t
269269+ val is_empty : t -> bool
270270+ val jsont : t Jsont.t
271271+ end
167272168273 type output = {
169274 decision: decision option;
170275 reason: string option;
276276+ additional_context: string option;
277277+ unknown: Output_unknown.t;
171278 }
172279173173- val of_json : Ezjsonm.value -> t
280280+ val output_jsont : output Jsont.t
281281+282282+ val continue : ?additional_context:string -> ?unknown:Output_unknown.t -> unit -> output
283283+ val block : ?reason:string -> ?unknown:Output_unknown.t -> unit -> output
284284+ val output_to_json : output -> Jsont.json
285285+end
286286+287287+(** Stop hook - fires when conversation stops *)
288288+module Stop : sig
289289+ module Input_unknown : sig
290290+ type t = Jsont.json
291291+ val empty : t
292292+ val is_empty : t -> bool
293293+ val jsont : t Jsont.t
294294+ end
295295+296296+ type input = {
297297+ session_id: string;
298298+ transcript_path: string;
299299+ stop_hook_active: bool;
300300+ unknown: Input_unknown.t;
301301+ }
302302+303303+ type t = input
304304+305305+ val of_json : Jsont.json -> t
174306175307 val session_id : t -> string
176308 val transcript_path : t -> string
177309 val stop_hook_active : t -> bool
178178- val raw_json : t -> Ezjsonm.value
310310+ val unknown : t -> Input_unknown.t
311311+312312+ val input_jsont : input Jsont.t
313313+314314+ module Output_unknown : sig
315315+ type t = Jsont.json
316316+ val empty : t
317317+ val is_empty : t -> bool
318318+ val jsont : t Jsont.t
319319+ end
320320+321321+ type output = {
322322+ decision: decision option;
323323+ reason: string option;
324324+ unknown: Output_unknown.t;
325325+ }
179326180180- val continue : unit -> output
181181- val block : ?reason:string -> unit -> output
182182- val output_to_json : output -> Ezjsonm.value
327327+ val output_jsont : output Jsont.t
328328+329329+ val continue : ?unknown:Output_unknown.t -> unit -> output
330330+ val block : ?reason:string -> ?unknown:Output_unknown.t -> unit -> output
331331+ val output_to_json : output -> Jsont.json
183332end
184333185334(** SubagentStop hook - fires when a subagent stops *)
186335module SubagentStop : sig
187336 include module type of Stop
188188- val of_json : Ezjsonm.value -> t
189189- val raw_json : t -> Ezjsonm.value
337337+ val of_json : Jsont.json -> t
190338end
191339192340(** PreCompact hook - fires before message compaction *)
193341module PreCompact : sig
194194- type t
342342+ module Input_unknown : sig
343343+ type t = Jsont.json
344344+ val empty : t
345345+ val is_empty : t -> bool
346346+ val jsont : t Jsont.t
347347+ end
348348+349349+ type input = {
350350+ session_id: string;
351351+ transcript_path: string;
352352+ unknown: Input_unknown.t;
353353+ }
354354+355355+ type t = input
356356+195357 type output = unit
196358197197- val of_json : Ezjsonm.value -> t
359359+ val of_json : Jsont.json -> t
198360199361 val session_id : t -> string
200362 val transcript_path : t -> string
201201- val raw_json : t -> Ezjsonm.value
363363+ val unknown : t -> Input_unknown.t
364364+365365+ val input_jsont : input Jsont.t
202366203367 val continue : unit -> output
204204- val output_to_json : output -> Ezjsonm.value
368368+ val output_to_json : output -> Jsont.json
205369end
206370207371(** {1 Callbacks} *)
···216380 And return a generic [result] with optional hook-specific output.
217381*)
218382type callback =
219219- input:Ezjsonm.value ->
383383+ input:Jsont.json ->
220384 tool_use_id:string option ->
221385 context:Context.t ->
222386 result
···234398235399(** {1 Generic Result Builders} *)
236400237237-(** [continue ?system_message ?hook_specific_output ()] creates a continue result *)
238238-val continue : ?system_message:string -> ?hook_specific_output:Ezjsonm.value -> unit -> result
401401+(** [continue ?system_message ?hook_specific_output ?unknown ()] creates a continue result *)
402402+val continue : ?system_message:string -> ?hook_specific_output:Jsont.json -> ?unknown:Result_unknown.t -> unit -> result
239403240240-(** [block ?system_message ?hook_specific_output ()] creates a block result *)
241241-val block : ?system_message:string -> ?hook_specific_output:Ezjsonm.value -> unit -> result
404404+(** [block ?system_message ?hook_specific_output ?unknown ()] creates a block result *)
405405+val block : ?system_message:string -> ?hook_specific_output:Jsont.json -> ?unknown:Result_unknown.t -> unit -> result
242406243407(** {1 Configuration Builders} *)
244408···253417254418(** {1 JSON Serialization} *)
255419256256-val result_to_json : result -> Ezjsonm.value
257257-val config_to_protocol_format : config -> Ezjsonm.value
420420+val result_to_json : result -> Jsont.json
421421+val config_to_protocol_format : config -> Jsont.json
+55
claudeio/lib/incoming.ml
···11+let src = Logs.Src.create "claude.incoming" ~doc:"Incoming messages from Claude CLI"
22+module Log = (val Logs.src_log src : Logs.LOG)
33+44+type t =
55+ | Message of Message.t
66+ | Control_response of Sdk_control.control_response
77+88+let jsont : t Jsont.t =
99+ (* Custom decoder that checks the type field and dispatches to the appropriate codec.
1010+1111+ The challenge is that Message can have multiple type values ("user", "assistant",
1212+ "system", "result"), while control_response has only one type value. Jsont's
1313+ case_mem discriminator doesn't support multiple tags per case, so we implement
1414+ a custom decoder/encoder. *)
1515+1616+ let dec json =
1717+ (* First check if it has a type field *)
1818+ match json with
1919+ | Jsont.Object (members, _meta) ->
2020+ let type_field = List.find_map (fun ((name, _), value) ->
2121+ if name = "type" then
2222+ match value with
2323+ | Jsont.String (s, _) -> Some s
2424+ | _ -> None
2525+ else None
2626+ ) members in
2727+ (match type_field with
2828+ | Some "control_response" ->
2929+ (match Jsont.Json.decode Sdk_control.control_response_jsont json with
3030+ | Ok resp -> Control_response resp
3131+ | Error err -> failwith ("Failed to decode control_response: " ^ err))
3232+ | Some ("user" | "assistant" | "system" | "result") | Some _ | None ->
3333+ (* Try to decode as message *)
3434+ (match Jsont.Json.decode Message.jsont json with
3535+ | Ok msg -> Message msg
3636+ | Error err -> failwith ("Failed to decode message: " ^ err)))
3737+ | _ -> failwith "Expected JSON object for incoming message"
3838+ in
3939+4040+ let enc = function
4141+ | Message msg ->
4242+ (match Jsont.Json.encode Message.jsont msg with
4343+ | Ok json -> json
4444+ | Error err -> failwith ("Failed to encode message: " ^ err))
4545+ | Control_response resp ->
4646+ (match Jsont.Json.encode Sdk_control.control_response_jsont resp with
4747+ | Ok json -> json
4848+ | Error err -> failwith ("Failed to encode control response: " ^ err))
4949+ in
5050+5151+ Jsont.map ~kind:"Incoming" ~dec ~enc Jsont.json
5252+5353+let pp fmt = function
5454+ | Message msg -> Format.fprintf fmt "@[<2>Message@ %a@]" Message.pp msg
5555+ | Control_response resp -> Format.fprintf fmt "@[<2>ControlResponse@ %a@]" Sdk_control.pp (Sdk_control.Response resp)
+22
claudeio/lib/incoming.mli
···11+(** Incoming messages from the Claude CLI.
22+33+ This module defines a discriminated union of all possible message types
44+ that can be received from the Claude CLI, with a single jsont codec.
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" is handled separately in the client (not incoming to SDK user)
1010+1111+ This provides a clean, type-safe way to decode incoming messages in a single
1212+ operation, avoiding the parse-then-switch-then-parse pattern. *)
1313+1414+type t =
1515+ | Message of Message.t
1616+ | Control_response of Sdk_control.control_response
1717+1818+val jsont : t Jsont.t
1919+(** Codec for incoming messages. Uses the "type" field to discriminate. *)
2020+2121+val pp : Format.formatter -> t -> unit
2222+(** [pp fmt t] pretty-prints the incoming message. *)
···11-(** JSON utility functions for working with Ezjsonm.
22-33- This module provides convenience combinators that combine common
44- Ezjsonm operations. Most functions are thin wrappers that combine
55- find/get operations or provide Option-based error handling. *)
66-77-(** {2 Finding values by path}
88-99- These combine [Ezjsonm.find] with type extraction functions. *)
1010-1111-val find_string : Ezjsonm.value -> string list -> string
1212-val find_int : Ezjsonm.value -> string list -> int
1313-val find_bool : Ezjsonm.value -> string list -> bool
1414-val find_float : Ezjsonm.value -> string list -> float
1515-1616-val find_string_opt : Ezjsonm.value -> string list -> string option
1717-val find_int_opt : Ezjsonm.value -> string list -> int option
1818-val find_bool_opt : Ezjsonm.value -> string list -> bool option
1919-val find_float_opt : Ezjsonm.value -> string list -> float option
2020-2121-(** {2 Association list operations} *)
2222-2323-val assoc_string : string -> (string * Ezjsonm.value) list -> string
2424-val assoc_int : string -> (string * Ezjsonm.value) list -> int
2525-val assoc_bool : string -> (string * Ezjsonm.value) list -> bool
2626-val assoc_float : string -> (string * Ezjsonm.value) list -> float
2727-2828-val assoc_string_opt : string -> (string * Ezjsonm.value) list -> string option
2929-val assoc_int_opt : string -> (string * Ezjsonm.value) list -> int option
3030-val assoc_bool_opt : string -> (string * Ezjsonm.value) list -> bool option
3131-val assoc_float_opt : string -> (string * Ezjsonm.value) list -> float option
3232-3333-(** {2 Object field operations}
3434-3535- Direct field access without needing to build paths. *)
3636-3737-(** Alias for [Ezjsonm.get_dict] *)
3838-val get_fields : Ezjsonm.value -> (string * Ezjsonm.value) list
3939-4040-val get_field : Ezjsonm.value -> string -> Ezjsonm.value
4141-val get_field_opt : Ezjsonm.value -> string -> Ezjsonm.value option
4242-4343-val get_field_string : Ezjsonm.value -> string -> string
4444-val get_field_int : Ezjsonm.value -> string -> int
4545-val get_field_bool : Ezjsonm.value -> string -> bool
4646-val get_field_float : Ezjsonm.value -> string -> float
4747-4848-val get_field_string_opt : Ezjsonm.value -> string -> string option
4949-val get_field_int_opt : Ezjsonm.value -> string -> int option
5050-val get_field_bool_opt : Ezjsonm.value -> string -> bool option
5151-val get_field_float_opt : Ezjsonm.value -> string -> float option
+396-179
claudeio/lib/message.ml
···11-open Ezjsonm
22-module JU = Json_utils
33-41let src = Logs.Src.create "claude.message" ~doc:"Claude messages"
52module Log = (val Logs.src_log src : Logs.LOG)
637485module User = struct
99- type content =
66+ type content =
107 | String of string
118 | Blocks of Content_block.t list
1212-1313- type t = { content : content }
1414-1515- let create_string s = { content = String s }
1616- let create_blocks blocks = { content = Blocks blocks }
1717-99+1010+ module Unknown = struct
1111+ type t = Jsont.json
1212+ let empty = Jsont.Object ([], Jsont.Meta.none)
1313+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
1414+ let jsont = Jsont.json
1515+ end
1616+1717+ type t = {
1818+ content : content;
1919+ unknown : Unknown.t;
2020+ }
2121+2222+ let create_string s = { content = String s; unknown = Unknown.empty }
2323+ let create_blocks blocks = { content = Blocks blocks; unknown = Unknown.empty }
2424+1825 let create_with_tool_result ~tool_use_id ~content ?is_error () =
1926 let tool_result = Content_block.tool_result ~tool_use_id ~content ?is_error () in
2020- { content = Blocks [tool_result] }
2121-2727+ { content = Blocks [tool_result]; unknown = Unknown.empty }
2828+2229 let create_mixed ~text ~tool_results =
2323- let blocks =
3030+ let blocks =
2431 let text_blocks = match text with
2532 | Some t -> [Content_block.text t]
2633 | None -> []
···3037 ) tool_results in
3138 text_blocks @ tool_blocks
3239 in
3333- { content = Blocks blocks }
3434-4040+ { content = Blocks blocks; unknown = Unknown.empty }
4141+4242+ let make content unknown = { content; unknown }
3543 let content t = t.content
3636-4444+ let unknown t = t.unknown
4545+3746 let as_text t = match t.content with
3847 | String s -> Some s
3948 | Blocks _ -> None
4040-4949+4150 let get_blocks t = match t.content with
4251 | String s -> [Content_block.text s]
4352 | Blocks blocks -> blocks
4444-5353+5454+ (* Decode content from json value *)
5555+ let decode_content json = match json with
5656+ | Jsont.String (s, _) -> String s
5757+ | Jsont.Array (items, _) ->
5858+ let blocks = List.map (fun j ->
5959+ match Jsont.Json.decode Content_block.jsont j with
6060+ | Ok b -> b
6161+ | Error msg -> failwith ("Invalid content block: " ^ msg)
6262+ ) items in
6363+ Blocks blocks
6464+ | _ -> failwith "Content must be string or array"
6565+6666+ (* Encode content to json value *)
6767+ let encode_content = function
6868+ | String s -> Jsont.String (s, Jsont.Meta.none)
6969+ | Blocks blocks -> Jsont.Array (List.map Content_block.to_json blocks, Jsont.Meta.none)
7070+7171+ let jsont : t Jsont.t =
7272+ Jsont.Object.map ~kind:"User" (fun json_content unknown ->
7373+ let content = decode_content json_content in
7474+ make content unknown
7575+ )
7676+ |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t -> encode_content (content t))
7777+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
7878+ |> Jsont.Object.finish
7979+4580 let to_json t =
4681 let content_json = match t.content with
4747- | String s -> `String s
4848- | Blocks blocks ->
4949- `A (List.map Content_block.to_json blocks)
8282+ | String s -> Jsont.String (s, Jsont.Meta.none)
8383+ | Blocks blocks ->
8484+ Jsont.Array (List.map Content_block.to_json blocks, Jsont.Meta.none)
5085 in
5151- `O [
5252- ("type", `String "user");
5353- ("message", `O [
5454- ("role", `String "user");
5555- ("content", content_json);
5656- ]);
5757- ]
5858-5959- let of_json = function
6060- | `O fields ->
6161- let message = List.assoc "message" fields in
8686+ Jsont.Object ([
8787+ (Jsont.Json.name "type", Jsont.String ("user", Jsont.Meta.none));
8888+ (Jsont.Json.name "message", Jsont.Object ([
8989+ (Jsont.Json.name "role", Jsont.String ("user", Jsont.Meta.none));
9090+ (Jsont.Json.name "content", content_json);
9191+ ], Jsont.Meta.none));
9292+ ], Jsont.Meta.none)
9393+9494+ let of_json json =
9595+ match json with
9696+ | Jsont.Object (fields, _) ->
9797+ let message = List.assoc (Jsont.Json.name "message") fields in
6298 let content = match message with
6363- | `O msg_fields ->
6464- (match List.assoc "content" msg_fields with
6565- | `String s -> String s
6666- | `A blocks -> Blocks (List.map Content_block.of_json blocks)
9999+ | Jsont.Object (msg_fields, _) ->
100100+ (match List.assoc (Jsont.Json.name "content") msg_fields with
101101+ | Jsont.String (s, _) -> String s
102102+ | Jsont.Array (items, _) ->
103103+ Blocks (List.map Content_block.of_json items)
67104 | _ -> raise (Invalid_argument "User.of_json: invalid content"))
68105 | _ -> raise (Invalid_argument "User.of_json: invalid message")
69106 in
7070- { content }
107107+ { content; unknown = Unknown.empty }
71108 | _ -> raise (Invalid_argument "User.of_json: expected object")
7272-109109+73110 let pp fmt t =
74111 match t.content with
7575- | String s ->
112112+ | String s ->
76113 if String.length s > 60 then
77114 let truncated = String.sub s 0 57 in
78115 Fmt.pf fmt "@[<2>User:@ %s...@]" truncated
79116 else
80117 Fmt.pf fmt "@[<2>User:@ %S@]" s
81118 | Blocks blocks ->
8282- let text_count = List.length (List.filter (function
119119+ let text_count = List.length (List.filter (function
83120 | Content_block.Text _ -> true | _ -> false) blocks) in
8484- let tool_result_count = List.length (List.filter (function
121121+ let tool_result_count = List.length (List.filter (function
85122 | Content_block.Tool_result _ -> true | _ -> false) blocks) in
86123 match text_count, tool_result_count with
8787- | 1, 0 ->
124124+ | 1, 0 ->
88125 let text = List.find_map (function
89126 | Content_block.Text t -> Some (Content_block.Text.text t)
90127 | _ -> None) blocks in
···123160 | "server_error" -> `Server_error
124161 | "unknown" | _ -> `Unknown
125162163163+ let error_jsont : error Jsont.t =
164164+ Jsont.enum [
165165+ ("authentication_failed", `Authentication_failed);
166166+ ("billing_error", `Billing_error);
167167+ ("rate_limit", `Rate_limit);
168168+ ("invalid_request", `Invalid_request);
169169+ ("server_error", `Server_error);
170170+ ("unknown", `Unknown);
171171+ ]
172172+173173+ module Unknown = struct
174174+ type t = Jsont.json
175175+ let empty = Jsont.Object ([], Jsont.Meta.none)
176176+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
177177+ let jsont = Jsont.json
178178+ end
179179+126180 type t = {
127181 content : Content_block.t list;
128182 model : string;
129183 error : error option;
184184+ unknown : Unknown.t;
130185 }
131186132132- let create ~content ~model ?error () = { content; model; error }
187187+ let create ~content ~model ?error () = { content; model; error; unknown = Unknown.empty }
188188+ let make content model error unknown = { content; model; error; unknown }
133189 let content t = t.content
134190 let model t = t.model
135191 let error t = t.error
136136-192192+ let unknown t = t.unknown
193193+137194 let get_text_blocks t =
138195 List.filter_map (function
139196 | Content_block.Text text -> Some (Content_block.Text.text text)
140197 | _ -> None
141198 ) t.content
142142-199199+143200 let get_tool_uses t =
144201 List.filter_map (function
145202 | Content_block.Tool_use tool -> Some tool
146203 | _ -> None
147204 ) t.content
148148-205205+149206 let get_thinking t =
150207 List.filter_map (function
151208 | Content_block.Thinking thinking -> Some thinking
152209 | _ -> None
153210 ) t.content
154154-211211+155212 let has_tool_use t =
156213 List.exists (function
157214 | Content_block.Tool_use _ -> true
158215 | _ -> false
159216 ) t.content
160160-217217+161218 let combined_text t =
162219 String.concat "\n" (get_text_blocks t)
163163-220220+221221+ let jsont : t Jsont.t =
222222+ Jsont.Object.map ~kind:"Assistant" make
223223+ |> Jsont.Object.mem "content" (Jsont.list Content_block.jsont) ~enc:content
224224+ |> Jsont.Object.mem "model" Jsont.string ~enc:model
225225+ |> Jsont.Object.opt_mem "error" error_jsont ~enc:error
226226+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
227227+ |> Jsont.Object.finish
228228+164229 let to_json t =
165230 let msg_fields = [
166166- ("content", `A (List.map Content_block.to_json t.content));
167167- ("model", `String t.model);
231231+ (Jsont.Json.name "content", Jsont.Array (List.map Content_block.to_json t.content, Jsont.Meta.none));
232232+ (Jsont.Json.name "model", Jsont.String (t.model, Jsont.Meta.none));
168233 ] in
169234 let msg_fields = match t.error with
170170- | Some err -> ("error", `String (error_to_string err)) :: msg_fields
235235+ | Some err -> (Jsont.Json.name "error", Jsont.String (error_to_string err, Jsont.Meta.none)) :: msg_fields
171236 | None -> msg_fields
172237 in
173173- `O [
174174- ("type", `String "assistant");
175175- ("message", `O msg_fields);
176176- ]
177177-178178- let of_json = function
179179- | `O fields ->
180180- let message = List.assoc "message" fields in
238238+ Jsont.Object ([
239239+ (Jsont.Json.name "type", Jsont.String ("assistant", Jsont.Meta.none));
240240+ (Jsont.Json.name "message", Jsont.Object (msg_fields, Jsont.Meta.none));
241241+ ], Jsont.Meta.none)
242242+243243+ let of_json json =
244244+ match json with
245245+ | Jsont.Object (fields, _) ->
246246+ let message = List.assoc (Jsont.Json.name "message") fields in
181247 let content, model, error = match message with
182182- | `O msg_fields ->
248248+ | Jsont.Object (msg_fields, _) ->
183249 let content =
184184- match List.assoc "content" msg_fields with
185185- | `A blocks -> List.map Content_block.of_json blocks
250250+ match List.assoc (Jsont.Json.name "content") msg_fields with
251251+ | Jsont.Array (items, _) -> List.map Content_block.of_json items
186252 | _ -> raise (Invalid_argument "Assistant.of_json: invalid content")
187253 in
188188- let model = JU.assoc_string "model" msg_fields in
254254+ let model = match List.assoc (Jsont.Json.name "model") msg_fields with
255255+ | Jsont.String (s, _) -> s
256256+ | _ -> raise (Invalid_argument "Assistant.of_json: invalid model")
257257+ in
189258 let error =
190190- match JU.assoc_string_opt "error" msg_fields with
191191- | Some err_str -> Some (error_of_string err_str)
259259+ match List.assoc_opt (Jsont.Json.name "error") msg_fields with
260260+ | Some (Jsont.String (err_str, _)) -> Some (error_of_string err_str)
261261+ | Some _ -> raise (Invalid_argument "Assistant.of_json: invalid error")
192262 | None -> None
193263 in
194264 content, model, error
195265 | _ -> raise (Invalid_argument "Assistant.of_json: invalid message")
196266 in
197197- { content; model; error }
267267+ { content; model; error; unknown = Unknown.empty }
198268 | _ -> raise (Invalid_argument "Assistant.of_json: expected object")
199199-269269+200270 let pp fmt t =
201271 let text_count = List.length (get_text_blocks t) in
202272 let tool_count = List.length (get_tool_uses t) in
···219289 | _ ->
220290 (* Mixed content *)
221291 let parts = [] in
222222- let parts = if text_count > 0 then
292292+ let parts = if text_count > 0 then
223293 Printf.sprintf "%d text" text_count :: parts else parts in
224224- let parts = if tool_count > 0 then
294294+ let parts = if tool_count > 0 then
225295 Printf.sprintf "%d tools" tool_count :: parts else parts in
226226- let parts = if thinking_count > 0 then
296296+ let parts = if thinking_count > 0 then
227297 Printf.sprintf "%d thinking" thinking_count :: parts else parts in
228298 Fmt.pf fmt "@[<2>Assistant@ [%s]:@ %s@]"
229299 t.model (String.concat ", " (List.rev parts))
···231301232302module System = struct
233303 module Data = struct
234234- (* Store both the raw JSON and provide typed accessors *)
235235- type t = value (* The full JSON data *)
236236-237237- let empty = `O []
238238-239239- let of_assoc assoc = `O assoc
240240-241241- let get_string t key = JU.get_field_string_opt t key
242242-243243- let get_int t key = JU.get_field_int_opt t key
244244-245245- let get_bool t key = JU.get_field_bool_opt t key
246246-247247- let get_float t key = JU.get_field_float_opt t key
248248-249249- let get_list t key =
250250- match t with
251251- | `O fields ->
252252- (match List.assoc_opt key fields with
253253- | Some (`A lst) -> Some lst
254254- | _ -> None)
255255- | _ -> None
256256-304304+ (* Opaque JSON type with typed accessors *)
305305+ type t = Jsont.json
306306+307307+ let jsont = Jsont.json
308308+309309+ let empty = Jsont.Object ([], Jsont.Meta.none)
310310+311311+ let of_assoc (assoc : (string * Jsont.json) list) : t =
312312+ Jsont.Object (
313313+ List.map (fun (k, v) -> (Jsont.Json.name k, v)) assoc,
314314+ Jsont.Meta.none
315315+ )
316316+257317 let get_field t key =
258318 match t with
259259- | `O fields -> List.assoc_opt key fields
319319+ | Jsont.Object (members, _) ->
320320+ List.find_map (fun ((name, _), value) ->
321321+ if name = key then Some value else None
322322+ ) members
260323 | _ -> None
261261-324324+325325+ let get_string t key =
326326+ match get_field t key with
327327+ | Some (Jsont.String (s, _)) -> Some s
328328+ | _ -> None
329329+330330+ let get_int t key =
331331+ match get_field t key with
332332+ | Some (Jsont.Number (f, _)) ->
333333+ let i = int_of_float f in
334334+ if float_of_int i = f then Some i else None
335335+ | _ -> None
336336+337337+ let get_bool t key =
338338+ match get_field t key with
339339+ | Some (Jsont.Bool (b, _)) -> Some b
340340+ | _ -> None
341341+342342+ let get_float t key =
343343+ match get_field t key with
344344+ | Some (Jsont.Number (f, _)) -> Some f
345345+ | _ -> None
346346+347347+ let get_list t key =
348348+ match get_field t key with
349349+ | Some (Jsont.Array (items, _)) -> Some items
350350+ | _ -> None
351351+262352 let raw_json t = t
263263-353353+264354 let to_json t = t
265355 let of_json json = json
266356 end
267267-357357+358358+ module Unknown = struct
359359+ type t = Jsont.json
360360+ let empty = Jsont.Object ([], Jsont.Meta.none)
361361+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
362362+ let jsont = Jsont.json
363363+ end
364364+268365 type t = {
269366 subtype : string;
270367 data : Data.t;
368368+ unknown : Unknown.t;
271369 }
272272-273273- let create ~subtype ~data = { subtype; data }
370370+371371+ let create ~subtype ~data = { subtype; data; unknown = Unknown.empty }
372372+ let make subtype data unknown = { subtype; data; unknown }
274373 let subtype t = t.subtype
275374 let data t = t.data
276276-375375+ let unknown t = t.unknown
376376+377377+ let jsont : t Jsont.t =
378378+ Jsont.Object.map ~kind:"System" make
379379+ |> Jsont.Object.mem "subtype" Jsont.string ~enc:subtype
380380+ |> Jsont.Object.mem "data" Data.jsont ~enc:data
381381+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
382382+ |> Jsont.Object.finish
383383+277384 let to_json t =
278278- `O [
279279- ("type", `String "system");
280280- ("subtype", `String t.subtype);
281281- ("data", Data.to_json t.data);
282282- ]
283283-284284- let of_json = function
285285- | `O fields ->
286286- let subtype = JU.assoc_string "subtype" fields in
287287- let data = Data.of_json (try List.assoc "data" fields with Not_found -> `O fields) in
288288- { subtype; data }
385385+ Jsont.Object ([
386386+ (Jsont.Json.name "type", Jsont.String ("system", Jsont.Meta.none));
387387+ (Jsont.Json.name "subtype", Jsont.String (t.subtype, Jsont.Meta.none));
388388+ (Jsont.Json.name "data", Data.to_json t.data);
389389+ ], Jsont.Meta.none)
390390+391391+ let of_json json =
392392+ match json with
393393+ | Jsont.Object (fields, _) ->
394394+ let subtype = match List.assoc (Jsont.Json.name "subtype") fields with
395395+ | Jsont.String (s, _) -> s
396396+ | _ -> raise (Invalid_argument "System.of_json: invalid subtype")
397397+ in
398398+ let data = Data.of_json (
399399+ try List.assoc (Jsont.Json.name "data") fields
400400+ with Not_found -> Jsont.Object (fields, Jsont.Meta.none)
401401+ ) in
402402+ { subtype; data; unknown = Unknown.empty }
289403 | _ -> raise (Invalid_argument "System.of_json: expected object")
290290-404404+291405 let pp fmt t =
292406 match t.subtype with
293407 | "init" ->
···308422309423module Result = struct
310424 module Usage = struct
311311- type t = value
312312-313313- let create ?input_tokens ?output_tokens ?total_tokens
425425+ (* Opaque JSON type with typed accessors *)
426426+ type t = Jsont.json
427427+428428+ let jsont = Jsont.json
429429+430430+ let create ?input_tokens ?output_tokens ?total_tokens
314431 ?cache_creation_input_tokens ?cache_read_input_tokens () =
315432 let fields = [] in
316433 let fields = match input_tokens with
317317- | Some n -> ("input_tokens", `Float (float_of_int n)) :: fields
434434+ | Some n -> (Jsont.Json.name "input_tokens", Jsont.Number (float_of_int n, Jsont.Meta.none)) :: fields
318435 | None -> fields in
319436 let fields = match output_tokens with
320320- | Some n -> ("output_tokens", `Float (float_of_int n)) :: fields
437437+ | Some n -> (Jsont.Json.name "output_tokens", Jsont.Number (float_of_int n, Jsont.Meta.none)) :: fields
321438 | None -> fields in
322439 let fields = match total_tokens with
323323- | Some n -> ("total_tokens", `Float (float_of_int n)) :: fields
440440+ | Some n -> (Jsont.Json.name "total_tokens", Jsont.Number (float_of_int n, Jsont.Meta.none)) :: fields
324441 | None -> fields in
325442 let fields = match cache_creation_input_tokens with
326326- | Some n -> ("cache_creation_input_tokens", `Float (float_of_int n)) :: fields
443443+ | Some n -> (Jsont.Json.name "cache_creation_input_tokens", Jsont.Number (float_of_int n, Jsont.Meta.none)) :: fields
327444 | None -> fields in
328445 let fields = match cache_read_input_tokens with
329329- | Some n -> ("cache_read_input_tokens", `Float (float_of_int n)) :: fields
446446+ | Some n -> (Jsont.Json.name "cache_read_input_tokens", Jsont.Number (float_of_int n, Jsont.Meta.none)) :: fields
330447 | None -> fields in
331331- `O fields
332332-333333- let input_tokens t = JU.get_field_int_opt t "input_tokens"
334334-335335- let output_tokens t = JU.get_field_int_opt t "output_tokens"
336336-337337- let total_tokens t = JU.get_field_int_opt t "total_tokens"
338338-339339- let cache_creation_input_tokens t = JU.get_field_int_opt t "cache_creation_input_tokens"
340340-341341- let cache_read_input_tokens t = JU.get_field_int_opt t "cache_read_input_tokens"
342342-448448+ Jsont.Object (fields, Jsont.Meta.none)
449449+450450+ let get_field t key =
451451+ match t with
452452+ | Jsont.Object (members, _) ->
453453+ List.find_map (fun ((name, _), value) ->
454454+ if name = key then Some value else None
455455+ ) members
456456+ | _ -> None
457457+458458+ let get_int t key =
459459+ match get_field t key with
460460+ | Some (Jsont.Number (f, _)) ->
461461+ let i = int_of_float f in
462462+ if float_of_int i = f then Some i else None
463463+ | _ -> None
464464+465465+ let input_tokens t = get_int t "input_tokens"
466466+467467+ let output_tokens t = get_int t "output_tokens"
468468+469469+ let total_tokens t = get_int t "total_tokens"
470470+471471+ let cache_creation_input_tokens t = get_int t "cache_creation_input_tokens"
472472+473473+ let cache_read_input_tokens t = get_int t "cache_read_input_tokens"
474474+343475 let effective_input_tokens t =
344476 match input_tokens t with
345477 | None -> 0
346478 | Some input ->
347479 let cached = Option.value (cache_read_input_tokens t) ~default:0 in
348480 max 0 (input - cached)
349349-481481+350482 let total_cost_estimate t ~input_price ~output_price =
351483 match input_tokens t, output_tokens t with
352484 | Some input, Some output ->
···354486 let output_cost = float_of_int output *. output_price /. 1_000_000. in
355487 Some (input_cost +. output_cost)
356488 | _ -> None
357357-489489+358490 let pp fmt t =
359491 Fmt.pf fmt "@[<2>Usage@ { input = %a;@ output = %a;@ total = %a;@ \
360492 cache_creation = %a;@ cache_read = %a }@]"
···363495 Fmt.(option int) (total_tokens t)
364496 Fmt.(option int) (cache_creation_input_tokens t)
365497 Fmt.(option int) (cache_read_input_tokens t)
366366-498498+367499 let to_json t = t
368500 let of_json json = json
369501 end
370370-502502+503503+ module Unknown = struct
504504+ type t = Jsont.json
505505+ let empty = Jsont.Object ([], Jsont.Meta.none)
506506+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
507507+ let jsont = Jsont.json
508508+ end
509509+371510 type t = {
372511 subtype : string;
373512 duration_ms : int;
···378517 total_cost_usd : float option;
379518 usage : Usage.t option;
380519 result : string option;
381381- structured_output : value option;
520520+ structured_output : Jsont.json option;
521521+ unknown : Unknown.t;
382522 }
383383-523523+384524 let create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
385525 ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
386526 { subtype; duration_ms; duration_api_ms; is_error; num_turns;
387387- session_id; total_cost_usd; usage; result; structured_output }
388388-527527+ session_id; total_cost_usd; usage; result; structured_output; unknown = Unknown.empty }
528528+529529+ let make subtype duration_ms duration_api_ms is_error num_turns
530530+ session_id total_cost_usd usage result structured_output unknown =
531531+ { subtype; duration_ms; duration_api_ms; is_error; num_turns;
532532+ session_id; total_cost_usd; usage; result; structured_output; unknown }
533533+389534 let subtype t = t.subtype
390535 let duration_ms t = t.duration_ms
391536 let duration_api_ms t = t.duration_api_ms
···396541 let usage t = t.usage
397542 let result t = t.result
398543 let structured_output t = t.structured_output
399399-544544+ let unknown t = t.unknown
545545+546546+ let jsont : t Jsont.t =
547547+ Jsont.Object.map ~kind:"Result" make
548548+ |> Jsont.Object.mem "subtype" Jsont.string ~enc:subtype
549549+ |> Jsont.Object.mem "duration_ms" Jsont.int ~enc:duration_ms
550550+ |> Jsont.Object.mem "duration_api_ms" Jsont.int ~enc:duration_api_ms
551551+ |> Jsont.Object.mem "is_error" Jsont.bool ~enc:is_error
552552+ |> Jsont.Object.mem "num_turns" Jsont.int ~enc:num_turns
553553+ |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
554554+ |> Jsont.Object.opt_mem "total_cost_usd" Jsont.number ~enc:total_cost_usd
555555+ |> Jsont.Object.opt_mem "usage" Usage.jsont ~enc:usage
556556+ |> Jsont.Object.opt_mem "result" Jsont.string ~enc:result
557557+ |> Jsont.Object.opt_mem "structured_output" Jsont.json ~enc:structured_output
558558+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
559559+ |> Jsont.Object.finish
560560+400561 let to_json t =
401562 let fields = [
402402- ("type", `String "result");
403403- ("subtype", `String t.subtype);
404404- ("duration_ms", `Float (float_of_int t.duration_ms));
405405- ("duration_api_ms", `Float (float_of_int t.duration_api_ms));
406406- ("is_error", `Bool t.is_error);
407407- ("num_turns", `Float (float_of_int t.num_turns));
408408- ("session_id", `String t.session_id);
563563+ (Jsont.Json.name "type", Jsont.String ("result", Jsont.Meta.none));
564564+ (Jsont.Json.name "subtype", Jsont.String (t.subtype, Jsont.Meta.none));
565565+ (Jsont.Json.name "duration_ms", Jsont.Number (float_of_int t.duration_ms, Jsont.Meta.none));
566566+ (Jsont.Json.name "duration_api_ms", Jsont.Number (float_of_int t.duration_api_ms, Jsont.Meta.none));
567567+ (Jsont.Json.name "is_error", Jsont.Bool (t.is_error, Jsont.Meta.none));
568568+ (Jsont.Json.name "num_turns", Jsont.Number (float_of_int t.num_turns, Jsont.Meta.none));
569569+ (Jsont.Json.name "session_id", Jsont.String (t.session_id, Jsont.Meta.none));
409570 ] in
410571 let fields = match t.total_cost_usd with
411411- | Some cost -> ("total_cost_usd", `Float cost) :: fields
572572+ | Some cost -> (Jsont.Json.name "total_cost_usd", Jsont.Number (cost, Jsont.Meta.none)) :: fields
412573 | None -> fields
413574 in
414575 let fields = match t.usage with
415415- | Some usage -> ("usage", Usage.to_json usage) :: fields
576576+ | Some usage -> (Jsont.Json.name "usage", Usage.to_json usage) :: fields
416577 | None -> fields
417578 in
418579 let fields = match t.result with
419419- | Some result -> ("result", `String result) :: fields
580580+ | Some result -> (Jsont.Json.name "result", Jsont.String (result, Jsont.Meta.none)) :: fields
420581 | None -> fields
421582 in
422583 let fields = match t.structured_output with
423423- | Some output -> ("structured_output", output) :: fields
584584+ | Some output -> (Jsont.Json.name "structured_output", output) :: fields
424585 | None -> fields
425586 in
426426- `O fields
427427-428428- let of_json = function
429429- | `O fields ->
430430- let subtype = JU.assoc_string "subtype" fields in
431431- let duration_ms = int_of_float (JU.assoc_float "duration_ms" fields) in
432432- let duration_api_ms = int_of_float (JU.assoc_float "duration_api_ms" fields) in
433433- let is_error = JU.assoc_bool "is_error" fields in
434434- let num_turns = int_of_float (JU.assoc_float "num_turns" fields) in
435435- let session_id = JU.assoc_string "session_id" fields in
436436- let total_cost_usd = JU.assoc_float_opt "total_cost_usd" fields in
437437- let usage = Option.map Usage.of_json (List.assoc_opt "usage" fields) in
438438- let result = JU.assoc_string_opt "result" fields in
439439- let structured_output = List.assoc_opt "structured_output" fields in
587587+ Jsont.Object (fields, Jsont.Meta.none)
588588+589589+ let of_json json =
590590+ match json with
591591+ | Jsont.Object (fields, _) ->
592592+ let subtype = match List.assoc (Jsont.Json.name "subtype") fields with
593593+ | Jsont.String (s, _) -> s
594594+ | _ -> raise (Invalid_argument "Result.of_json: invalid subtype")
595595+ in
596596+ let duration_ms = match List.assoc (Jsont.Json.name "duration_ms") fields with
597597+ | Jsont.Number (f, _) -> int_of_float f
598598+ | _ -> raise (Invalid_argument "Result.of_json: invalid duration_ms")
599599+ in
600600+ let duration_api_ms = match List.assoc (Jsont.Json.name "duration_api_ms") fields with
601601+ | Jsont.Number (f, _) -> int_of_float f
602602+ | _ -> raise (Invalid_argument "Result.of_json: invalid duration_api_ms")
603603+ in
604604+ let is_error = match List.assoc (Jsont.Json.name "is_error") fields with
605605+ | Jsont.Bool (b, _) -> b
606606+ | _ -> raise (Invalid_argument "Result.of_json: invalid is_error")
607607+ in
608608+ let num_turns = match List.assoc (Jsont.Json.name "num_turns") fields with
609609+ | Jsont.Number (f, _) -> int_of_float f
610610+ | _ -> raise (Invalid_argument "Result.of_json: invalid num_turns")
611611+ in
612612+ let session_id = match List.assoc (Jsont.Json.name "session_id") fields with
613613+ | Jsont.String (s, _) -> s
614614+ | _ -> raise (Invalid_argument "Result.of_json: invalid session_id")
615615+ in
616616+ let total_cost_usd = match List.assoc_opt (Jsont.Json.name "total_cost_usd") fields with
617617+ | Some (Jsont.Number (f, _)) -> Some f
618618+ | Some _ -> raise (Invalid_argument "Result.of_json: invalid total_cost_usd")
619619+ | None -> None
620620+ in
621621+ let usage = Option.map Usage.of_json (List.assoc_opt (Jsont.Json.name "usage") fields) in
622622+ let result = match List.assoc_opt (Jsont.Json.name "result") fields with
623623+ | Some (Jsont.String (s, _)) -> Some s
624624+ | Some _ -> raise (Invalid_argument "Result.of_json: invalid result")
625625+ | None -> None
626626+ in
627627+ let structured_output = List.assoc_opt (Jsont.Json.name "structured_output") fields in
440628 { subtype; duration_ms; duration_api_ms; is_error; num_turns;
441441- session_id; total_cost_usd; usage; result; structured_output }
629629+ session_id; total_cost_usd; usage; result; structured_output; unknown = Unknown.empty }
442630 | _ -> raise (Invalid_argument "Result.of_json: expected object")
443443-631631+444632 let pp fmt t =
445633 if t.is_error then
446634 Fmt.pf fmt "@[<2>Result.error@ { session = %S;@ result = %a }@]"
···461649 | None -> ""
462650 in
463651 Fmt.pf fmt "@[<2>Result.%s@ { duration = %dms;@ cost = $%.4f%s }@]"
464464- t.subtype
652652+ t.subtype
465653 t.duration_ms
466654 (Option.value t.total_cost_usd ~default:0.0)
467655 tokens_info
···484672485673let system ~subtype ~data = System (System.create ~subtype ~data)
486674let system_init ~session_id =
487487- let data = System.Data.of_assoc [("session_id", `String session_id)] in
675675+ let data = System.Data.of_assoc [(("session_id", Jsont.String (session_id, Jsont.Meta.none)))] in
488676 System (System.create ~subtype:"init" ~data)
489677let system_error ~error =
490490- let data = System.Data.of_assoc [("error", `String error)] in
678678+ let data = System.Data.of_assoc [(("error", Jsont.String (error, Jsont.Meta.none)))] in
491679 System (System.create ~subtype:"error" ~data)
492680493681let result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
···495683 Result (Result.create ~subtype ~duration_ms ~duration_api_ms ~is_error
496684 ~num_turns ~session_id ?total_cost_usd ?usage ?result ?structured_output ())
497685686686+(* Jsont codec for the main Message variant type *)
687687+let jsont : t Jsont.t =
688688+ let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
689689+690690+ let case_user = case_map "user" User.jsont (fun v -> User v) in
691691+ let case_assistant = case_map "assistant" Assistant.jsont (fun v -> Assistant v) in
692692+ let case_system = case_map "system" System.jsont (fun v -> System v) in
693693+ let case_result = case_map "result" Result.jsont (fun v -> Result v) in
694694+695695+ let enc_case = function
696696+ | User v -> Jsont.Object.Case.value case_user v
697697+ | Assistant v -> Jsont.Object.Case.value case_assistant v
698698+ | System v -> Jsont.Object.Case.value case_system v
699699+ | Result v -> Jsont.Object.Case.value case_result v
700700+ in
701701+702702+ let cases = Jsont.Object.Case.[
703703+ make case_user;
704704+ make case_assistant;
705705+ make case_system;
706706+ make case_result
707707+ ] in
708708+709709+ Jsont.Object.map ~kind:"Message" Fun.id
710710+ |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
711711+ ~tag_to_string:Fun.id ~tag_compare:String.compare
712712+ |> Jsont.Object.finish
713713+498714let to_json = function
499715 | User t -> User.to_json t
500716 | Assistant t -> Assistant.to_json t
···503719504720let of_json json =
505721 match json with
506506- | `O fields -> (
507507- match List.assoc_opt "type" fields with
508508- | Some (`String "user") -> User (User.of_json json)
509509- | Some (`String "assistant") -> Assistant (Assistant.of_json json)
510510- | Some (`String "system") -> System (System.of_json json)
511511- | Some (`String "result") -> Result (Result.of_json json)
512512- | _ -> raise (Invalid_argument "Message.of_json: unknown type")
722722+ | Jsont.Object (fields, _) -> (
723723+ match List.assoc_opt (Jsont.Json.name "type") fields with
724724+ | Some (Jsont.String ("user", _)) -> User (User.of_json json)
725725+ | Some (Jsont.String ("assistant", _)) -> Assistant (Assistant.of_json json)
726726+ | Some (Jsont.String ("system", _)) -> System (System.of_json json)
727727+ | Some (Jsont.String ("result", _)) -> Result (Result.of_json json)
728728+ | Some _ -> raise (Invalid_argument "Message.of_json: invalid type")
729729+ | None -> raise (Invalid_argument "Message.of_json: missing type field")
513730 )
514731 | _ -> raise (Invalid_argument "Message.of_json: expected object")
515732
+171-110
claudeio/lib/message.mli
···11(** Messages exchanged with Claude.
22-33- This module defines the various types of messages that can be sent to and
44- received from Claude, including user input, assistant responses, system
22+33+ 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. *)
6677(** The log source for message operations *)
···11111212module User : sig
1313 (** Messages sent by the user. *)
1414-1515- type content =
1414+1515+ type content =
1616 | String of string (** Simple text message *)
1717 | Blocks of Content_block.t list (** Complex message with multiple content blocks *)
1818 (** The content of a user message. *)
1919-1919+2020+ module Unknown : sig
2121+ type t = Jsont.json
2222+ val empty : t
2323+ val is_empty : t -> bool
2424+ val jsont : t Jsont.t
2525+ end
2626+2027 type t
2128 (** The type of user messages. *)
2222-2929+3030+ val jsont : t Jsont.t
3131+ (** [jsont] is the Jsont codec for user messages. *)
3232+2333 val create_string : string -> t
2434 (** [create_string s] creates a user message with simple text content. *)
2525-3535+2636 val create_blocks : Content_block.t list -> t
2737 (** [create_blocks blocks] creates a user message with content blocks. *)
2828-2929- val create_with_tool_result :
3030- tool_use_id:string ->
3131- content:string ->
3232- ?is_error:bool ->
3838+3939+ val create_with_tool_result :
4040+ tool_use_id:string ->
4141+ content:string ->
4242+ ?is_error:bool ->
3343 unit -> t
3434- (** [create_with_tool_result ~tool_use_id ~content ?is_error ()] creates a user
4444+ (** [create_with_tool_result ~tool_use_id ~content ?is_error ()] creates a user
3545 message containing a tool result. *)
3636-4646+3747 val create_mixed : text:string option -> tool_results:(string * string * bool option) list -> t
3838- (** [create_mixed ?text ~tool_results] creates a user message with optional text
4848+ (** [create_mixed ?text ~tool_results] creates a user message with optional text
3949 and tool results. Each tool result is (tool_use_id, content, is_error). *)
4040-5050+4151 val content : t -> content
4252 (** [content t] returns the content of the user message. *)
4343-5353+5454+ val unknown : t -> Unknown.t
5555+ (** [unknown t] returns the unknown fields preserved from JSON. *)
5656+4457 val as_text : t -> string option
4558 (** [as_text t] returns the text content if the message is a simple string, None otherwise. *)
4646-5959+4760 val get_blocks : t -> Content_block.t list
4861 (** [get_blocks t] returns the content blocks, or a single text block if it's a string message. *)
4949-5050- val to_json : t -> Ezjsonm.value
6262+6363+ val to_json : t -> Jsont.json
5164 (** [to_json t] converts the user message to its JSON representation. *)
5252-5353- val of_json : Ezjsonm.value -> t
6565+6666+ val of_json : Jsont.json -> t
5467 (** [of_json json] parses a user message from JSON.
5568 @raise Invalid_argument if the JSON is not a valid user message. *)
5656-6969+5770 val pp : Format.formatter -> t -> unit
5871 (** [pp fmt t] pretty-prints the user message. *)
5972end
···7992 val error_of_string : string -> error
8093 (** [error_of_string s] parses an error string. Unknown strings become [`Unknown]. *)
81949595+ module Unknown : sig
9696+ type t = Jsont.json
9797+ val empty : t
9898+ val is_empty : t -> bool
9999+ val jsont : t Jsont.t
100100+ end
101101+82102 type t
83103 (** The type of assistant messages. *)
104104+105105+ val jsont : t Jsont.t
106106+ (** [jsont] is the Jsont codec for assistant messages. *)
8410785108 val create : content:Content_block.t list -> model:string -> ?error:error -> unit -> t
86109 (** [create ~content ~model ?error ()] creates an assistant message.
···9611997120 val error : t -> error option
98121 (** [error t] returns the optional error that occurred during message generation. *)
9999-122122+123123+ val unknown : t -> Unknown.t
124124+ (** [unknown t] returns the unknown fields preserved from JSON. *)
125125+100126 val get_text_blocks : t -> string list
101127 (** [get_text_blocks t] extracts all text content from the message. *)
102102-128128+103129 val get_tool_uses : t -> Content_block.Tool_use.t list
104130 (** [get_tool_uses t] extracts all tool use blocks from the message. *)
105105-131131+106132 val get_thinking : t -> Content_block.Thinking.t list
107133 (** [get_thinking t] extracts all thinking blocks from the message. *)
108108-134134+109135 val has_tool_use : t -> bool
110136 (** [has_tool_use t] returns true if the message contains any tool use blocks. *)
111111-137137+112138 val combined_text : t -> string
113139 (** [combined_text t] concatenates all text blocks into a single string. *)
114114-115115- val to_json : t -> Ezjsonm.value
140140+141141+ val to_json : t -> Jsont.json
116142 (** [to_json t] converts the assistant message to its JSON representation. *)
117117-118118- val of_json : Ezjsonm.value -> t
143143+144144+ val of_json : Jsont.json -> t
119145 (** [of_json json] parses an assistant message from JSON.
120146 @raise Invalid_argument if the JSON is not a valid assistant message. *)
121121-147147+122148 val pp : Format.formatter -> t -> unit
123149 (** [pp fmt t] pretty-prints the assistant message. *)
124150end
···127153128154module System : sig
129155 (** System control and status messages. *)
130130-156156+131157 module Data : sig
132158 (** System message data. *)
133133-134134- type t
135135- (** Abstract type for system message data. Contains both the raw JSON
136136- and typed accessors for common fields. *)
137137-159159+160160+ type t = Jsont.json
161161+ (** Opaque type for system message data. Contains the raw JSON
162162+ with typed accessors for common fields. *)
163163+164164+ val jsont : t Jsont.t
165165+ (** [jsont] is the Jsont codec for system data. *)
166166+138167 val empty : t
139168 (** [empty] creates empty data. *)
140140-141141- val of_assoc : (string * Ezjsonm.value) list -> t
169169+170170+ val of_assoc : (string * Jsont.json) list -> t
142171 (** [of_assoc assoc] creates data from an association list. *)
143143-172172+144173 val get_string : t -> string -> string option
145174 (** [get_string t key] returns the string value for [key], if present. *)
146146-175175+147176 val get_int : t -> string -> int option
148177 (** [get_int t key] returns the integer value for [key], if present. *)
149149-178178+150179 val get_bool : t -> string -> bool option
151180 (** [get_bool t key] returns the boolean value for [key], if present. *)
152152-181181+153182 val get_float : t -> string -> float option
154183 (** [get_float t key] returns the float value for [key], if present. *)
155155-156156- val get_list : t -> string -> Ezjsonm.value list option
184184+185185+ val get_list : t -> string -> Jsont.json list option
157186 (** [get_list t key] returns the list value for [key], if present. *)
158158-159159- val get_field : t -> string -> Ezjsonm.value option
187187+188188+ val get_field : t -> string -> Jsont.json option
160189 (** [get_field t key] returns the raw JSON value for [key], if present. *)
161161-162162- val raw_json : t -> Ezjsonm.value
190190+191191+ val raw_json : t -> Jsont.json
163192 (** [raw_json t] returns the full underlying JSON data. *)
164164-165165- val to_json : t -> Ezjsonm.value
193193+194194+ val to_json : t -> Jsont.json
166195 (** [to_json t] converts to JSON representation. Internal use only. *)
167167-168168- val of_json : Ezjsonm.value -> t
196196+197197+ val of_json : Jsont.json -> t
169198 (** [of_json json] parses from JSON. Internal use only. *)
170199 end
171171-200200+201201+ module Unknown : sig
202202+ type t = Jsont.json
203203+ val empty : t
204204+ val is_empty : t -> bool
205205+ val jsont : t Jsont.t
206206+ end
207207+172208 type t
173209 (** The type of system messages. *)
174174-210210+211211+ val jsont : t Jsont.t
212212+ (** [jsont] is the Jsont codec for system messages. *)
213213+175214 val create : subtype:string -> data:Data.t -> t
176215 (** [create ~subtype ~data] creates a system message.
177216 @param subtype The subtype of the system message
178217 @param data Additional data for the message *)
179179-218218+180219 val subtype : t -> string
181220 (** [subtype t] returns the subtype of the system message. *)
182182-221221+183222 val data : t -> Data.t
184223 (** [data t] returns the additional data of the system message. *)
185185-186186- val to_json : t -> Ezjsonm.value
224224+225225+ val unknown : t -> Unknown.t
226226+ (** [unknown t] returns the unknown fields preserved from JSON. *)
227227+228228+ val to_json : t -> Jsont.json
187229 (** [to_json t] converts the system message to its JSON representation. *)
188188-189189- val of_json : Ezjsonm.value -> t
230230+231231+ val of_json : Jsont.json -> t
190232 (** [of_json json] parses a system message from JSON.
191233 @raise Invalid_argument if the JSON is not a valid system message. *)
192192-234234+193235 val pp : Format.formatter -> t -> unit
194236 (** [pp fmt t] pretty-prints the system message. *)
195237end
···198240199241module Result : sig
200242 (** Final result messages with metadata about the conversation. *)
201201-243243+202244 module Usage : sig
203245 (** Usage statistics for API calls. *)
204204-205205- type t
206206- (** Abstract type for usage statistics. *)
207207-208208- val create :
209209- ?input_tokens:int ->
210210- ?output_tokens:int ->
246246+247247+ type t = Jsont.json
248248+ (** Opaque type for usage statistics. *)
249249+250250+ val jsont : t Jsont.t
251251+ (** [jsont] is the Jsont codec for usage statistics. *)
252252+253253+ val create :
254254+ ?input_tokens:int ->
255255+ ?output_tokens:int ->
211256 ?total_tokens:int ->
212257 ?cache_creation_input_tokens:int ->
213258 ?cache_read_input_tokens:int ->
214259 unit -> t
215215- (** [create ?input_tokens ?output_tokens ?total_tokens ?cache_creation_input_tokens
260260+ (** [create ?input_tokens ?output_tokens ?total_tokens ?cache_creation_input_tokens
216261 ?cache_read_input_tokens ()] creates usage statistics. *)
217217-262262+218263 val input_tokens : t -> int option
219264 (** [input_tokens t] returns the number of input tokens used. *)
220220-265265+221266 val output_tokens : t -> int option
222267 (** [output_tokens t] returns the number of output tokens generated. *)
223223-268268+224269 val total_tokens : t -> int option
225270 (** [total_tokens t] returns the total number of tokens. *)
226226-271271+227272 val cache_creation_input_tokens : t -> int option
228273 (** [cache_creation_input_tokens t] returns cache creation input tokens. *)
229229-274274+230275 val cache_read_input_tokens : t -> int option
231276 (** [cache_read_input_tokens t] returns cache read input tokens. *)
232232-277277+233278 val effective_input_tokens : t -> int
234279 (** [effective_input_tokens t] returns input tokens minus cached tokens, or 0 if not available. *)
235235-280280+236281 val total_cost_estimate : t -> input_price:float -> output_price:float -> float option
237237- (** [total_cost_estimate t ~input_price ~output_price] estimates the cost based on token
282282+ (** [total_cost_estimate t ~input_price ~output_price] estimates the cost based on token
238283 prices per million tokens. Returns None if token counts are not available. *)
239239-284284+240285 val pp : Format.formatter -> t -> unit
241286 (** [pp fmt t] pretty-prints the usage statistics. *)
242242-243243- val to_json : t -> Ezjsonm.value
287287+288288+ val to_json : t -> Jsont.json
244289 (** [to_json t] converts to JSON representation. Internal use only. *)
245245-246246- val of_json : Ezjsonm.value -> t
290290+291291+ val of_json : Jsont.json -> t
247292 (** [of_json json] parses from JSON. Internal use only. *)
248293 end
249249-294294+295295+ module Unknown : sig
296296+ type t = Jsont.json
297297+ val empty : t
298298+ val is_empty : t -> bool
299299+ val jsont : t Jsont.t
300300+ end
301301+250302 type t
251303 (** The type of result messages. *)
252252-304304+305305+ val jsont : t Jsont.t
306306+ (** [jsont] is the Jsont codec for result messages. *)
307307+253308 val create :
254309 subtype:string ->
255310 duration_ms:int ->
···260315 ?total_cost_usd:float ->
261316 ?usage:Usage.t ->
262317 ?result:string ->
263263- ?structured_output:Ezjsonm.value ->
318318+ ?structured_output:Jsont.json ->
264319 unit -> t
265265- (** [create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
320320+ (** [create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
266321 ~session_id ?total_cost_usd ?usage ?result ()] creates a result message.
267322 @param subtype The subtype of the result
268323 @param duration_ms Total duration in milliseconds
···274329 @param usage Optional usage statistics as JSON
275330 @param result Optional result string
276331 @param structured_output Optional structured JSON output from Claude *)
277277-332332+278333 val subtype : t -> string
279334 (** [subtype t] returns the subtype of the result. *)
280280-335335+281336 val duration_ms : t -> int
282337 (** [duration_ms t] returns the total duration in milliseconds. *)
283283-338338+284339 val duration_api_ms : t -> int
285340 (** [duration_api_ms t] returns the API duration in milliseconds. *)
286286-341341+287342 val is_error : t -> bool
288343 (** [is_error t] returns whether this result represents an error. *)
289289-344344+290345 val num_turns : t -> int
291346 (** [num_turns t] returns the number of conversation turns. *)
292292-347347+293348 val session_id : t -> string
294349 (** [session_id t] returns the session identifier. *)
295295-350350+296351 val total_cost_usd : t -> float option
297352 (** [total_cost_usd t] returns the optional total cost in USD. *)
298298-353353+299354 val usage : t -> Usage.t option
300355 (** [usage t] returns the optional usage statistics. *)
301301-356356+302357 val result : t -> string option
303358 (** [result t] returns the optional result string. *)
304359305305- val structured_output : t -> Ezjsonm.value option
360360+ val structured_output : t -> Jsont.json option
306361 (** [structured_output t] returns the optional structured JSON output. *)
307362308308- val to_json : t -> Ezjsonm.value
363363+ val unknown : t -> Unknown.t
364364+ (** [unknown t] returns the unknown fields preserved from JSON. *)
365365+366366+ val to_json : t -> Jsont.json
309367 (** [to_json t] converts the result message to its JSON representation. *)
310310-311311- val of_json : Ezjsonm.value -> t
368368+369369+ val of_json : Jsont.json -> t
312370 (** [of_json json] parses a result message from JSON.
313371 @raise Invalid_argument if the JSON is not a valid result message. *)
314314-372372+315373 val pp : Format.formatter -> t -> unit
316374 (** [pp fmt t] pretty-prints the result message. *)
317375end
···325383 | Result of Result.t
326384(** The type of messages, which can be user, assistant, system, or result. *)
327385386386+val jsont : t Jsont.t
387387+(** [jsont] is the Jsont codec for messages. *)
388388+328389val user_string : string -> t
329390(** [user_string s] creates a user message with text content. *)
330391···332393(** [user_blocks blocks] creates a user message with content blocks. *)
333394334395val user_with_tool_result : tool_use_id:string -> content:string -> ?is_error:bool -> unit -> t
335335-(** [user_with_tool_result ~tool_use_id ~content ?is_error ()] creates a user message
396396+(** [user_with_tool_result ~tool_use_id ~content ?is_error ()] creates a user message
336397 containing a tool result. *)
337398338399val assistant : content:Content_block.t list -> model:string -> ?error:Assistant.error -> unit -> t
···360421 ?total_cost_usd:float ->
361422 ?usage:Result.Usage.t ->
362423 ?result:string ->
363363- ?structured_output:Ezjsonm.value ->
424424+ ?structured_output:Jsont.json ->
364425 unit -> t
365365-(** [result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
426426+(** [result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
366427 ~session_id ?total_cost_usd ?usage ?result ()] creates a result message. *)
367428368368-val to_json : t -> Ezjsonm.value
429429+val to_json : t -> Jsont.json
369430(** [to_json t] converts any message to its JSON representation. *)
370431371371-val of_json : Ezjsonm.value -> t
432432+val of_json : Jsont.json -> t
372433(** [of_json json] parses a message from JSON.
373434 @raise Invalid_argument if the JSON is not a valid message. *)
374435
···11+(** Claude AI model identifiers.
22+33+ This module provides type-safe model identifiers based on the Python SDK's
44+ model strings. 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+]
1515+(** The type of Claude models. *)
1616+1717+val to_string : t -> string
1818+(** [to_string t] converts a model to its CLI string representation.
1919+2020+ Examples:
2121+ - [`Sonnet_4_5] becomes "claude-sonnet-4-5"
2222+ - [`Opus_4] becomes "claude-opus-4"
2323+ - [`Custom "my-model"] becomes "my-model" *)
2424+2525+val of_string : string -> t
2626+(** [of_string s] parses a model string into a typed model.
2727+2828+ Known model strings are converted to their typed variants.
2929+ Unknown strings become [`Custom s].
3030+3131+ Examples:
3232+ - "claude-sonnet-4-5" becomes [`Sonnet_4_5]
3333+ - "future-model" becomes [`Custom "future-model"] *)
3434+3535+val pp : Format.formatter -> t -> unit
3636+(** [pp fmt t] pretty-prints a model identifier. *)
+82-99
claudeio/lib/options.ml
···11-open Ezjsonm
22-31let src = Logs.Src.create "claude.options" ~doc:"Claude configuration options"
42module Log = (val Logs.src_log src : Logs.LOG)
5364type setting_source = User | Project | Local
7566+module Unknown = struct
77+ type t = Jsont.json
88+ let empty = Jsont.Object ([], Jsont.Meta.none)
99+ let _is_empty = function Jsont.Object ([], _) -> true | _ -> false
1010+ let _jsont = Jsont.json
1111+end
1212+813type t = {
914 allowed_tools : string list;
1015 disallowed_tools : string list;
···3136 max_buffer_size : int option;
3237 user : string option;
3338 output_format : Structured_output.t option;
3939+ unknown : Unknown.t;
3440}
35413642let default = {
···5965 max_buffer_size = None;
6066 user = None;
6167 output_format = None;
6868+ unknown = Unknown.empty;
6269}
63706471let create
···8794 ?max_buffer_size
8895 ?user
8996 ?output_format
9797+ ?(unknown = Unknown.empty)
9098 () =
9199 { allowed_tools; disallowed_tools; max_thinking_tokens;
92100 system_prompt; append_system_prompt; permission_mode;
···95103 permission_prompt_tool_name; settings; add_dirs;
96104 extra_args; debug_stderr; hooks;
97105 max_budget_usd; fallback_model; setting_sources;
9898- max_buffer_size; user; output_format }
106106+ max_buffer_size; user; output_format; unknown }
99107100108let allowed_tools t = t.allowed_tools
101109let disallowed_tools t = t.disallowed_tools
···122130let max_buffer_size t = t.max_buffer_size
123131let user t = t.user
124132let output_format t = t.output_format
133133+let unknown t = t.unknown
125134126135let with_allowed_tools tools t = { t with allowed_tools = tools }
127136let with_disallowed_tools tools t = { t with disallowed_tools = tools }
···152161let with_user user t = { t with user = Some user }
153162let with_output_format format t = { t with output_format = Some format }
154163164164+(* Helper codec for Model.t *)
165165+let model_jsont : Model.t Jsont.t =
166166+ Jsont.map ~kind:"Model"
167167+ ~dec:Model.of_string
168168+ ~enc:Model.to_string
169169+ Jsont.string
170170+171171+(* Helper codec for env - list of string pairs encoded as object *)
172172+let env_jsont : (string * string) list Jsont.t =
173173+ Jsont.map ~kind:"Env"
174174+ ~dec:(fun obj ->
175175+ match obj with
176176+ | Jsont.Object (members, _) ->
177177+ List.map (fun ((name, _), value) ->
178178+ match value with
179179+ | Jsont.String (s, _) -> (name, s)
180180+ | _ -> (name, "")
181181+ ) members
182182+ | _ -> [])
183183+ ~enc:(fun pairs ->
184184+ let mems = List.map (fun (k, v) ->
185185+ Jsont.Json.mem (Jsont.Json.name k) (Jsont.Json.string v)
186186+ ) pairs in
187187+ Jsont.Json.object' mems)
188188+ Jsont.json
189189+190190+let jsont : t Jsont.t =
191191+ let make allowed_tools disallowed_tools max_thinking_tokens
192192+ system_prompt append_system_prompt permission_mode
193193+ model env unknown =
194194+ { allowed_tools; disallowed_tools; max_thinking_tokens;
195195+ system_prompt; append_system_prompt; permission_mode;
196196+ permission_callback = Some Permissions.default_allow_callback;
197197+ model; cwd = None; env;
198198+ continue_conversation = false;
199199+ resume = None;
200200+ max_turns = None;
201201+ permission_prompt_tool_name = None;
202202+ settings = None;
203203+ add_dirs = [];
204204+ extra_args = [];
205205+ debug_stderr = None;
206206+ hooks = None;
207207+ max_budget_usd = None;
208208+ fallback_model = None;
209209+ setting_sources = None;
210210+ max_buffer_size = None;
211211+ user = None;
212212+ output_format = None;
213213+ unknown }
214214+ in
215215+ Jsont.Object.map ~kind:"Options" make
216216+ |> Jsont.Object.mem "allowed_tools" (Jsont.list Jsont.string) ~enc:allowed_tools ~dec_absent:[]
217217+ |> Jsont.Object.mem "disallowed_tools" (Jsont.list Jsont.string) ~enc:disallowed_tools ~dec_absent:[]
218218+ |> Jsont.Object.mem "max_thinking_tokens" Jsont.int ~enc:max_thinking_tokens ~dec_absent:8000
219219+ |> Jsont.Object.opt_mem "system_prompt" Jsont.string ~enc:system_prompt
220220+ |> Jsont.Object.opt_mem "append_system_prompt" Jsont.string ~enc:append_system_prompt
221221+ |> Jsont.Object.opt_mem "permission_mode" Permissions.Mode.jsont ~enc:permission_mode
222222+ |> Jsont.Object.opt_mem "model" model_jsont ~enc:model
223223+ |> Jsont.Object.mem "env" env_jsont ~enc:env ~dec_absent:[]
224224+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
225225+ |> Jsont.Object.finish
226226+155227let to_json t =
156156- let fields = [] in
157157- let fields =
158158- if t.allowed_tools <> [] then
159159- ("allowed_tools", `A (List.map (fun s -> `String s) t.allowed_tools)) :: fields
160160- else fields
161161- in
162162- let fields =
163163- if t.disallowed_tools <> [] then
164164- ("disallowed_tools", `A (List.map (fun s -> `String s) t.disallowed_tools)) :: fields
165165- else fields
166166- in
167167- let fields =
168168- if t.max_thinking_tokens <> 8000 then
169169- ("max_thinking_tokens", `Float (float_of_int t.max_thinking_tokens)) :: fields
170170- else fields
171171- in
172172- let fields = match t.system_prompt with
173173- | Some p -> ("system_prompt", `String p) :: fields
174174- | None -> fields
175175- in
176176- let fields = match t.append_system_prompt with
177177- | Some p -> ("append_system_prompt", `String p) :: fields
178178- | None -> fields
179179- in
180180- let fields = match t.permission_mode with
181181- | Some m -> ("permission_mode", Permissions.Mode.to_json m) :: fields
182182- | None -> fields
183183- in
184184- let fields = match t.model with
185185- | Some m -> ("model", `String (Model.to_string m)) :: fields
186186- | None -> fields
187187- in
188188- let fields =
189189- if t.env <> [] then
190190- let env_obj = `O (List.map (fun (k, v) -> (k, `String v)) t.env) in
191191- ("env", env_obj) :: fields
192192- else fields
193193- in
194194- `O fields
228228+ match Jsont.Json.encode jsont t with
229229+ | Ok json -> json
230230+ | Error msg -> failwith ("Options.to_json: " ^ msg)
195231196196-let of_json = function
197197- | `O fields ->
198198- let allowed_tools =
199199- try get_list get_string (List.assoc "allowed_tools" fields)
200200- with Not_found -> []
201201- in
202202- let disallowed_tools =
203203- try get_list get_string (List.assoc "disallowed_tools" fields)
204204- with Not_found -> []
205205- in
206206- let max_thinking_tokens =
207207- try int_of_float (get_float (List.assoc "max_thinking_tokens" fields))
208208- with Not_found -> 8000
209209- in
210210- let system_prompt =
211211- try Some (get_string (List.assoc "system_prompt" fields))
212212- with Not_found -> None
213213- in
214214- let append_system_prompt =
215215- try Some (get_string (List.assoc "append_system_prompt" fields))
216216- with Not_found -> None
217217- in
218218- let permission_mode =
219219- try Some (Permissions.Mode.of_json (List.assoc "permission_mode" fields))
220220- with Not_found -> None
221221- in
222222- let model =
223223- try Some (Model.of_string (get_string (List.assoc "model" fields)))
224224- with Not_found -> None
225225- in
226226- let env =
227227- try
228228- match List.assoc "env" fields with
229229- | `O pairs -> List.map (fun (k, v) -> (k, get_string v)) pairs
230230- | _ -> []
231231- with Not_found -> []
232232- in
233233- { allowed_tools; disallowed_tools; max_thinking_tokens;
234234- system_prompt; append_system_prompt; permission_mode;
235235- permission_callback = Some Permissions.default_allow_callback;
236236- model; cwd = None; env;
237237- continue_conversation = false;
238238- resume = None;
239239- max_turns = None;
240240- permission_prompt_tool_name = None;
241241- settings = None;
242242- add_dirs = [];
243243- extra_args = [];
244244- debug_stderr = None;
245245- hooks = None;
246246- max_budget_usd = None;
247247- fallback_model = None;
248248- setting_sources = None;
249249- max_buffer_size = None;
250250- user = None;
251251- output_format = None; }
252252- | _ -> raise (Invalid_argument "Options.of_json: expected object")
232232+let of_json json =
233233+ match Jsont.Json.decode jsont json with
234234+ | Ok t -> t
235235+ | Error msg -> raise (Invalid_argument ("Options.of_json: " ^ msg))
253236254237let pp fmt t =
255238 Fmt.pf fmt "@[<v>Options {@ \
+10-5
claudeio/lib/options.mli
···6262 {3 Structured Output: Type-Safe Responses}
63636464 {[
6565- let schema = Ezjsonm.(`O [
6565+ let schema = Jsont.json_of_json (`O [
6666 ("type", `String "object");
6767 ("properties", `O [
6868 ("count", `O [("type", `String "integer")]);
···98989999 Use {!with_fallback_model} to specify an alternative model when the
100100 primary model is unavailable or overloaded. This improves reliability. *)
101101-102102-open Ezjsonm
103101104102(** The log source for options operations *)
105103val src : Logs.Src.t
···148146 ?max_buffer_size:int ->
149147 ?user:string ->
150148 ?output_format:Structured_output.t ->
149149+ ?unknown:Jsont.json ->
151150 unit -> t
152151(** [create ?allowed_tools ?disallowed_tools ?max_thinking_tokens ?system_prompt
153152 ?append_system_prompt ?permission_mode ?permission_callback ?model ?cwd ?env
···257256258257val output_format : t -> Structured_output.t option
259258(** [output_format t] returns the optional structured output format. *)
259259+260260+val unknown : t -> Jsont.json
261261+(** [unknown t] returns any unknown JSON fields that were preserved during decoding. *)
260262261263(** {1 Builders} *)
262264···351353352354(** {1 Serialization} *)
353355354354-val to_json : t -> value
356356+val jsont : t Jsont.t
357357+(** [jsont] is the Jsont codec for Options.t *)
358358+359359+val to_json : t -> Jsont.json
355360(** [to_json t] converts options to JSON representation. *)
356361357357-val of_json : value -> t
362362+val of_json : Jsont.json -> t
358363(** [of_json json] parses options from JSON.
359364 @raise Invalid_argument if the JSON is not valid options. *)
360365
+192-168
claudeio/lib/permissions.ml
···11-open Ezjsonm
22-31let src = Logs.Src.create "claude.permission" ~doc:"Claude permission system"
42module Log = (val Logs.src_log src : Logs.LOG)
5364(* Helper for pretty-printing JSON *)
75let pp_json fmt json =
88- Fmt.string fmt (value_to_string json)
66+ let s = match Jsont_bytesrw.encode_string' Jsont.json json with
77+ | Ok s -> s
88+ | Error err -> Jsont.Error.to_string err
99+ in
1010+ Fmt.string fmt s
9111012(** Permission modes *)
1113module Mode = struct
1212- type t =
1414+ type t =
1315 | Default
1416 | Accept_edits
1517 | Plan
1618 | Bypass_permissions
1717-1919+1820 let to_string = function
1921 | Default -> "default"
2022 | Accept_edits -> "acceptEdits"
2123 | Plan -> "plan"
2224 | Bypass_permissions -> "bypassPermissions"
2323-2525+2426 let of_string = function
2527 | "default" -> Default
2628 | "acceptEdits" -> Accept_edits
2729 | "plan" -> Plan
2830 | "bypassPermissions" -> Bypass_permissions
2931 | s -> raise (Invalid_argument (Printf.sprintf "Mode.of_string: unknown mode %s" s))
3030-3131- let to_json t = `String (to_string t)
3232-3333- let of_json = function
3434- | `String s -> of_string s
3535- | _ -> raise (Invalid_argument "Mode.of_json: expected string")
3636-3232+3733 let pp fmt t = Fmt.string fmt (to_string t)
3434+3535+ let jsont : t Jsont.t =
3636+ Jsont.enum [
3737+ "default", Default;
3838+ "acceptEdits", Accept_edits;
3939+ "plan", Plan;
4040+ "bypassPermissions", Bypass_permissions;
4141+ ]
3842end
39434044(** Permission behaviors *)
4145module Behavior = struct
4246 type t = Allow | Deny | Ask
4343-4747+4448 let to_string = function
4549 | Allow -> "allow"
4650 | Deny -> "deny"
4751 | Ask -> "ask"
4848-5252+4953 let of_string = function
5054 | "allow" -> Allow
5155 | "deny" -> Deny
5256 | "ask" -> Ask
5357 | s -> raise (Invalid_argument (Printf.sprintf "Behavior.of_string: unknown behavior %s" s))
5454-5555- let to_json t = `String (to_string t)
5656-5757- let of_json = function
5858- | `String s -> of_string s
5959- | _ -> raise (Invalid_argument "Behavior.of_json: expected string")
6060-5858+6159 let pp fmt t = Fmt.string fmt (to_string t)
6060+6161+ let jsont : t Jsont.t =
6262+ Jsont.enum [
6363+ "allow", Allow;
6464+ "deny", Deny;
6565+ "ask", Ask;
6666+ ]
6267end
63686469(** Permission rules *)
6570module Rule = struct
7171+ module Unknown = struct
7272+ type t = Jsont.json
7373+ let empty = Jsont.Object ([], Jsont.Meta.none)
7474+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
7575+ let jsont = Jsont.json
7676+ end
7777+6678 type t = {
6779 tool_name : string;
6880 rule_content : string option;
8181+ unknown : Unknown.t;
6982 }
7070-7171- let create ~tool_name ?rule_content () = { tool_name; rule_content }
8383+8484+ let create ~tool_name ?rule_content ?(unknown = Unknown.empty) () =
8585+ { tool_name; rule_content; unknown }
7286 let tool_name t = t.tool_name
7387 let rule_content t = t.rule_content
7474-7575- let to_json t =
7676- let fields = [("tool_name", `String t.tool_name)] in
7777- let fields = match t.rule_content with
7878- | Some c -> ("rule_content", `String c) :: fields
7979- | None -> fields
8080- in
8181- `O fields
8282-8383- let of_json = function
8484- | `O fields ->
8585- let tool_name = get_string (List.assoc "tool_name" fields) in
8686- let rule_content =
8787- try Some (get_string (List.assoc "rule_content" fields))
8888- with Not_found -> None
8989- in
9090- { tool_name; rule_content }
9191- | _ -> raise (Invalid_argument "Rule.of_json: expected object")
9292-8888+ let unknown t = t.unknown
8989+9090+ let jsont : t Jsont.t =
9191+ let make tool_name rule_content unknown = { tool_name; rule_content; unknown } in
9292+ Jsont.Object.map ~kind:"Rule" make
9393+ |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
9494+ |> Jsont.Object.opt_mem "rule_content" Jsont.string ~enc:rule_content
9595+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
9696+ |> Jsont.Object.finish
9797+9398 let pp fmt t =
9499 Fmt.pf fmt "@[<2>Rule@ { tool_name = %S;@ rule_content = %a }@]"
95100 t.tool_name Fmt.(option string) t.rule_content
···9710298103(** Permission updates *)
99104module Update = struct
100100- type destination =
105105+ type destination =
101106 | User_settings
102107 | Project_settings
103108 | Local_settings
104109 | Session
105105-110110+106111 let destination_to_string = function
107112 | User_settings -> "userSettings"
108113 | Project_settings -> "projectSettings"
109114 | Local_settings -> "localSettings"
110115 | Session -> "session"
111111-112112- let destination_of_string = function
116116+117117+ let _destination_of_string = function
113118 | "userSettings" -> User_settings
114119 | "projectSettings" -> Project_settings
115120 | "localSettings" -> Local_settings
116121 | "session" -> Session
117122 | s -> raise (Invalid_argument (Printf.sprintf "destination_of_string: unknown %s" s))
118118-123123+124124+ let destination_jsont : destination Jsont.t =
125125+ Jsont.enum [
126126+ "userSettings", User_settings;
127127+ "projectSettings", Project_settings;
128128+ "localSettings", Local_settings;
129129+ "session", Session;
130130+ ]
131131+119132 type update_type =
120133 | Add_rules
121134 | Replace_rules
···123136 | Set_mode
124137 | Add_directories
125138 | Remove_directories
126126-139139+127140 let update_type_to_string = function
128141 | Add_rules -> "addRules"
129142 | Replace_rules -> "replaceRules"
···131144 | Set_mode -> "setMode"
132145 | Add_directories -> "addDirectories"
133146 | Remove_directories -> "removeDirectories"
134134-135135- let update_type_of_string = function
147147+148148+ let _update_type_of_string = function
136149 | "addRules" -> Add_rules
137150 | "replaceRules" -> Replace_rules
138151 | "removeRules" -> Remove_rules
···140153 | "addDirectories" -> Add_directories
141154 | "removeDirectories" -> Remove_directories
142155 | s -> raise (Invalid_argument (Printf.sprintf "update_type_of_string: unknown %s" s))
156156+157157+ let update_type_jsont : update_type Jsont.t =
158158+ Jsont.enum [
159159+ "addRules", Add_rules;
160160+ "replaceRules", Replace_rules;
161161+ "removeRules", Remove_rules;
162162+ "setMode", Set_mode;
163163+ "addDirectories", Add_directories;
164164+ "removeDirectories", Remove_directories;
165165+ ]
143166167167+ module Unknown = struct
168168+ type t = Jsont.json
169169+ let empty = Jsont.Object ([], Jsont.Meta.none)
170170+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
171171+ let jsont = Jsont.json
172172+ end
173173+144174 type t = {
145175 update_type : update_type;
146176 rules : Rule.t list option;
···148178 mode : Mode.t option;
149179 directories : string list option;
150180 destination : destination option;
181181+ unknown : Unknown.t;
151182 }
152152-153153- let create ~update_type ?rules ?behavior ?mode ?directories ?destination () =
154154- { update_type; rules; behavior; mode; directories; destination }
155155-183183+184184+ let create ~update_type ?rules ?behavior ?mode ?directories ?destination ?(unknown = Unknown.empty) () =
185185+ { update_type; rules; behavior; mode; directories; destination; unknown }
186186+156187 let update_type t = t.update_type
157188 let rules t = t.rules
158189 let behavior t = t.behavior
159190 let mode t = t.mode
160191 let directories t = t.directories
161192 let destination t = t.destination
162162-163163- let to_json t =
164164- let fields = [("type", `String (update_type_to_string t.update_type))] in
165165- let fields = match t.rules with
166166- | Some rules -> ("rules", `A (List.map Rule.to_json rules)) :: fields
167167- | None -> fields
193193+ let unknown t = t.unknown
194194+195195+ let jsont : t Jsont.t =
196196+ let make update_type rules behavior mode directories destination unknown =
197197+ { update_type; rules; behavior; mode; directories; destination; unknown }
168198 in
169169- let fields = match t.behavior with
170170- | Some b -> ("behavior", Behavior.to_json b) :: fields
171171- | None -> fields
172172- in
173173- let fields = match t.mode with
174174- | Some m -> ("mode", Mode.to_json m) :: fields
175175- | None -> fields
176176- in
177177- let fields = match t.directories with
178178- | Some dirs -> ("directories", `A (List.map (fun s -> `String s) dirs)) :: fields
179179- | None -> fields
180180- in
181181- let fields = match t.destination with
182182- | Some d -> ("destination", `String (destination_to_string d)) :: fields
183183- | None -> fields
184184- in
185185- `O fields
186186-187187- let of_json = function
188188- | `O fields ->
189189- let update_type = update_type_of_string (get_string (List.assoc "type" fields)) in
190190- let rules =
191191- try Some (get_list Rule.of_json (List.assoc "rules" fields))
192192- with Not_found -> None
193193- in
194194- let behavior =
195195- try Some (Behavior.of_json (List.assoc "behavior" fields))
196196- with Not_found -> None
197197- in
198198- let mode =
199199- try Some (Mode.of_json (List.assoc "mode" fields))
200200- with Not_found -> None
201201- in
202202- let directories =
203203- try Some (get_list get_string (List.assoc "directories" fields))
204204- with Not_found -> None
205205- in
206206- let destination =
207207- try Some (destination_of_string (get_string (List.assoc "destination" fields)))
208208- with Not_found -> None
209209- in
210210- { update_type; rules; behavior; mode; directories; destination }
211211- | _ -> raise (Invalid_argument "Update.of_json: expected object")
199199+ Jsont.Object.map ~kind:"Update" make
200200+ |> Jsont.Object.mem "type" update_type_jsont ~enc:update_type
201201+ |> Jsont.Object.opt_mem "rules" (Jsont.list Rule.jsont) ~enc:rules
202202+ |> Jsont.Object.opt_mem "behavior" Behavior.jsont ~enc:behavior
203203+ |> Jsont.Object.opt_mem "mode" Mode.jsont ~enc:mode
204204+ |> Jsont.Object.opt_mem "directories" (Jsont.list Jsont.string) ~enc:directories
205205+ |> Jsont.Object.opt_mem "destination" destination_jsont ~enc:destination
206206+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
207207+ |> Jsont.Object.finish
212208213209 let pp fmt t =
214210 Fmt.pf fmt "@[<2>Update@ { type = %s;@ rules = %a;@ behavior = %a;@ \
···223219224220(** Permission context for callbacks *)
225221module Context = struct
222222+ module Unknown = struct
223223+ type t = Jsont.json
224224+ let empty = Jsont.Object ([], Jsont.Meta.none)
225225+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
226226+ let jsont = Jsont.json
227227+ end
228228+226229 type t = {
227230 suggestions : Update.t list;
231231+ unknown : Unknown.t;
228232 }
229229-230230- let create ?(suggestions = []) () = { suggestions }
233233+234234+ let create ?(suggestions = []) ?(unknown = Unknown.empty) () = { suggestions; unknown }
231235 let suggestions t = t.suggestions
232232-233233- let to_json t =
234234- `O [("suggestions", `A (List.map Update.to_json t.suggestions))]
235235-236236- let of_json = function
237237- | `O fields ->
238238- let suggestions =
239239- try get_list Update.of_json (List.assoc "suggestions" fields)
240240- with Not_found -> []
241241- in
242242- { suggestions }
243243- | _ -> raise (Invalid_argument "Context.of_json: expected object")
244244-236236+ let unknown t = t.unknown
237237+238238+ let jsont : t Jsont.t =
239239+ let make suggestions unknown = { suggestions; unknown } in
240240+ Jsont.Object.map ~kind:"Context" make
241241+ |> Jsont.Object.mem "suggestions" (Jsont.list Update.jsont) ~enc:suggestions ~dec_absent:[]
242242+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
243243+ |> Jsont.Object.finish
244244+245245 let pp fmt t =
246246 Fmt.pf fmt "@[<2>Context@ { suggestions = @[<v>%a@] }@]"
247247 Fmt.(list ~sep:(any "@,") Update.pp) t.suggestions
···249249250250(** Permission results *)
251251module Result = struct
252252+ module Unknown = struct
253253+ type t = Jsont.json
254254+ let empty = Jsont.Object ([], Jsont.Meta.none)
255255+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
256256+ let jsont = Jsont.json
257257+ end
258258+252259 type t =
253260 | Allow of {
254254- updated_input : value option;
261261+ updated_input : Jsont.json option;
255262 updated_permissions : Update.t list option;
263263+ unknown : Unknown.t;
256264 }
257265 | Deny of {
258266 message : string;
259267 interrupt : bool;
268268+ unknown : Unknown.t;
260269 }
261261-262262- let allow ?updated_input ?updated_permissions () =
263263- Allow { updated_input; updated_permissions }
264264-265265- let deny ~message ~interrupt = Deny { message; interrupt }
266266-267267- let to_json = function
268268- | Allow { updated_input; updated_permissions } ->
269269- let fields = [("behavior", `String "allow")] in
270270- let fields = match updated_input with
271271- | Some input -> ("updated_input", input) :: fields
272272- | None -> fields
273273- in
274274- let fields = match updated_permissions with
275275- | Some perms -> ("updated_permissions", `A (List.map Update.to_json perms)) :: fields
276276- | None -> fields
277277- in
278278- `O fields
279279- | Deny { message; interrupt } ->
280280- `O [
281281- ("behavior", `String "deny");
282282- ("message", `String message);
283283- ("interrupt", `Bool interrupt);
284284- ]
285285-286286- let of_json = function
287287- | `O fields -> (
288288- match List.assoc "behavior" fields with
289289- | `String "allow" ->
290290- let updated_input = List.assoc_opt "updated_input" fields in
291291- let updated_permissions =
292292- try Some (get_list Update.of_json (List.assoc "updated_permissions" fields))
293293- with Not_found -> None
294294- in
295295- Allow { updated_input; updated_permissions }
296296- | `String "deny" ->
297297- let message = get_string (List.assoc "message" fields) in
298298- let interrupt = get_bool (List.assoc "interrupt" fields) in
299299- Deny { message; interrupt }
300300- | _ -> raise (Invalid_argument "Result.of_json: unknown behavior")
301301- )
302302- | _ -> raise (Invalid_argument "Result.of_json: expected object")
270270+271271+ let allow ?updated_input ?updated_permissions ?(unknown = Unknown.empty) () =
272272+ Allow { updated_input; updated_permissions; unknown }
273273+274274+ let deny ~message ~interrupt ?(unknown = Unknown.empty) () =
275275+ Deny { message; interrupt; unknown }
276276+277277+ let jsont : t Jsont.t =
278278+ let allow_record =
279279+ let make updated_input updated_permissions unknown =
280280+ Allow { updated_input; updated_permissions; unknown }
281281+ in
282282+ Jsont.Object.map ~kind:"AllowRecord" make
283283+ |> Jsont.Object.opt_mem "updated_input" Jsont.json ~enc:(function
284284+ | Allow { updated_input; _ } -> updated_input
285285+ | _ -> None)
286286+ |> Jsont.Object.opt_mem "updated_permissions" (Jsont.list Update.jsont) ~enc:(function
287287+ | Allow { updated_permissions; _ } -> updated_permissions
288288+ | _ -> None)
289289+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(function
290290+ | Allow { unknown; _ } -> unknown
291291+ | _ -> Unknown.empty)
292292+ |> Jsont.Object.finish
293293+ in
294294+ let deny_record =
295295+ let make message interrupt unknown =
296296+ Deny { message; interrupt; unknown }
297297+ in
298298+ Jsont.Object.map ~kind:"DenyRecord" make
299299+ |> Jsont.Object.mem "message" Jsont.string ~enc:(function
300300+ | Deny { message; _ } -> message
301301+ | _ -> "")
302302+ |> Jsont.Object.mem "interrupt" Jsont.bool ~enc:(function
303303+ | Deny { interrupt; _ } -> interrupt
304304+ | _ -> false)
305305+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(function
306306+ | Deny { unknown; _ } -> unknown
307307+ | _ -> Unknown.empty)
308308+ |> Jsont.Object.finish
309309+ in
310310+ let case_allow = Jsont.Object.Case.map "allow" allow_record ~dec:(fun v -> v) in
311311+ let case_deny = Jsont.Object.Case.map "deny" deny_record ~dec:(fun v -> v) in
312312+313313+ let enc_case = function
314314+ | Allow _ as v -> Jsont.Object.Case.value case_allow v
315315+ | Deny _ as v -> Jsont.Object.Case.value case_deny v
316316+ in
317317+318318+ let cases = Jsont.Object.Case.[
319319+ make case_allow;
320320+ make case_deny
321321+ ] in
322322+323323+ Jsont.Object.map ~kind:"Result" Fun.id
324324+ |> Jsont.Object.case_mem "behavior" Jsont.string ~enc:Fun.id ~enc_case cases
325325+ ~tag_to_string:Fun.id ~tag_compare:String.compare
326326+ |> Jsont.Object.finish
303327304328 let pp fmt = function
305305- | Allow { updated_input; updated_permissions } ->
329329+ | Allow { updated_input; updated_permissions; _ } ->
306330 Fmt.pf fmt "@[<2>Allow@ { updated_input = %a;@ updated_permissions = %a }@]"
307331 Fmt.(option pp_json) updated_input
308332 Fmt.(option (list Update.pp)) updated_permissions
309309- | Deny { message; interrupt } ->
333333+ | Deny { message; interrupt; _ } ->
310334 Fmt.pf fmt "@[<2>Deny@ { message = %S;@ interrupt = %b }@]" message interrupt
311335end
312336313337(** Permission callback type *)
314314-type callback =
315315- tool_name:string ->
316316- input:value ->
317317- context:Context.t ->
338338+type callback =
339339+ tool_name:string ->
340340+ input:Jsont.json ->
341341+ context:Context.t ->
318342 Result.t
319343320344(** Default callbacks *)
+127-106
claudeio/lib/permissions.mli
···11(** Permission system for Claude tool invocations.
22-22+33 This module provides a permission system for controlling
44 which tools Claude can invoke and how they can be used. It includes
55 support for permission modes, rules, updates, and callbacks. *)
66-77-open Ezjsonm
8697(** The log source for permission operations *)
108val src : Logs.Src.t
···13111412module Mode : sig
1513 (** Permission modes control the overall behavior of the permission system. *)
1616-1717- type t =
1414+1515+ type t =
1816 | Default (** Standard permission mode with normal checks *)
1917 | Accept_edits (** Automatically accept file edits *)
2018 | Plan (** Planning mode with restricted execution *)
2119 | Bypass_permissions (** Bypass all permission checks *)
2220 (** The type of permission modes. *)
2323-2121+2422 val to_string : t -> string
2523 (** [to_string t] converts a mode to its string representation. *)
2626-2424+2725 val of_string : string -> t
2826 (** [of_string s] parses a mode from its string representation.
2927 @raise Invalid_argument if the string is not a valid mode. *)
3030-3131- val to_json : t -> value
3232- (** [to_json t] converts a mode to JSON. *)
3333-3434- val of_json : value -> t
3535- (** [of_json json] parses a mode from JSON.
3636- @raise Invalid_argument if the JSON is not a valid mode. *)
3737-2828+3829 val pp : Format.formatter -> t -> unit
3930 (** [pp fmt t] pretty-prints the mode. *)
3131+3232+ val jsont : t Jsont.t
3333+ (** [jsont] is the Jsont codec for permission modes. *)
4034end
41354236(** {1 Permission Behaviors} *)
43374438module Behavior : sig
4539 (** Behaviors determine how permission requests are handled. *)
4646-4747- type t =
4040+4141+ type t =
4842 | Allow (** Allow the operation *)
4943 | Deny (** Deny the operation *)
5044 | Ask (** Ask the user for permission *)
5145 (** The type of permission behaviors. *)
5252-4646+5347 val to_string : t -> string
5448 (** [to_string t] converts a behavior to its string representation. *)
5555-4949+5650 val of_string : string -> t
5751 (** [of_string s] parses a behavior from its string representation.
5852 @raise Invalid_argument if the string is not a valid behavior. *)
5959-6060- val to_json : t -> value
6161- (** [to_json t] converts a behavior to JSON. *)
6262-6363- val of_json : value -> t
6464- (** [of_json json] parses a behavior from JSON.
6565- @raise Invalid_argument if the JSON is not a valid behavior. *)
6666-5353+6754 val pp : Format.formatter -> t -> unit
6855 (** [pp fmt t] pretty-prints the behavior. *)
5656+5757+ val jsont : t Jsont.t
5858+ (** [jsont] is the Jsont codec for permission behaviors. *)
6959end
70607161(** {1 Permission Rules} *)
72627363module Rule : sig
7464 (** Rules define specific permissions for tools. *)
7575-6565+6666+ module Unknown : sig
6767+ type t = Jsont.json
6868+ val empty : t
6969+ val is_empty : t -> bool
7070+ val jsont : t Jsont.t
7171+ end
7272+7673 type t = {
7774 tool_name : string; (** Name of the tool *)
7875 rule_content : string option; (** Optional rule specification *)
7676+ unknown : Unknown.t; (** Unknown fields *)
7977 }
8078 (** The type of permission rules. *)
8181-8282- val create : tool_name:string -> ?rule_content:string -> unit -> t
8383- (** [create ~tool_name ?rule_content ()] creates a new rule.
7979+8080+ val create : tool_name:string -> ?rule_content:string -> ?unknown:Unknown.t -> unit -> t
8181+ (** [create ~tool_name ?rule_content ?unknown ()] creates a new rule.
8482 @param tool_name The name of the tool this rule applies to
8585- @param rule_content Optional rule specification or pattern *)
8686-8383+ @param rule_content Optional rule specification or pattern
8484+ @param unknown Optional unknown fields to preserve *)
8585+8786 val tool_name : t -> string
8887 (** [tool_name t] returns the tool name. *)
8989-8888+9089 val rule_content : t -> string option
9190 (** [rule_content t] returns the optional rule content. *)
9292-9393- val to_json : t -> value
9494- (** [to_json t] converts a rule to JSON. *)
9595-9696- val of_json : value -> t
9797- (** [of_json json] parses a rule from JSON.
9898- @raise Invalid_argument if the JSON is not a valid rule. *)
9999-9191+9292+ val unknown : t -> Unknown.t
9393+ (** [unknown t] returns the unknown fields. *)
9494+10095 val pp : Format.formatter -> t -> unit
10196 (** [pp fmt t] pretty-prints the rule. *)
9797+9898+ val jsont : t Jsont.t
9999+ (** [jsont] is the Jsont codec for permission rules. *)
102100end
103101104102(** {1 Permission Updates} *)
105103106104module Update : sig
107105 (** Updates modify permission settings. *)
108108-109109- type destination =
106106+107107+ type destination =
110108 | User_settings (** Apply to user settings *)
111109 | Project_settings (** Apply to project settings *)
112110 | Local_settings (** Apply to local settings *)
113111 | Session (** Apply to current session only *)
114112 (** The destination for permission updates. *)
115115-113113+116114 type update_type =
117115 | Add_rules (** Add new rules *)
118116 | Replace_rules (** Replace existing rules *)
···121119 | Add_directories (** Add allowed directories *)
122120 | Remove_directories (** Remove allowed directories *)
123121 (** The type of permission update. *)
124124-122122+123123+ module Unknown : sig
124124+ type t = Jsont.json
125125+ val empty : t
126126+ val is_empty : t -> bool
127127+ val jsont : t Jsont.t
128128+ end
129129+125130 type t
126131 (** The type of permission updates. *)
127127-128128- val create :
129129- update_type:update_type ->
130130- ?rules:Rule.t list ->
131131- ?behavior:Behavior.t ->
132132- ?mode:Mode.t ->
133133- ?directories:string list ->
134134- ?destination:destination ->
132132+133133+ val create :
134134+ update_type:update_type ->
135135+ ?rules:Rule.t list ->
136136+ ?behavior:Behavior.t ->
137137+ ?mode:Mode.t ->
138138+ ?directories:string list ->
139139+ ?destination:destination ->
140140+ ?unknown:Unknown.t ->
135141 unit -> t
136136- (** [create ~update_type ?rules ?behavior ?mode ?directories ?destination ()]
142142+ (** [create ~update_type ?rules ?behavior ?mode ?directories ?destination ?unknown ()]
137143 creates a new permission update.
138144 @param update_type The type of update to perform
139145 @param rules Optional list of rules to add/remove/replace
140146 @param behavior Optional behavior to set
141147 @param mode Optional permission mode to set
142148 @param directories Optional directories to add/remove
143143- @param destination Optional destination for the update *)
144144-149149+ @param destination Optional destination for the update
150150+ @param unknown Optional unknown fields to preserve *)
151151+145152 val update_type : t -> update_type
146153 (** [update_type t] returns the update type. *)
147147-154154+148155 val rules : t -> Rule.t list option
149156 (** [rules t] returns the optional list of rules. *)
150150-157157+151158 val behavior : t -> Behavior.t option
152159 (** [behavior t] returns the optional behavior. *)
153153-160160+154161 val mode : t -> Mode.t option
155162 (** [mode t] returns the optional mode. *)
156156-163163+157164 val directories : t -> string list option
158165 (** [directories t] returns the optional list of directories. *)
159159-166166+160167 val destination : t -> destination option
161168 (** [destination t] returns the optional destination. *)
162162-163163- val to_json : t -> value
164164- (** [to_json t] converts an update to JSON. *)
165165-166166- val of_json : value -> t
167167- (** [of_json json] parses an update from JSON.
168168- @raise Invalid_argument if the JSON is not a valid update. *)
169169-169169+170170+ val unknown : t -> Unknown.t
171171+ (** [unknown t] returns the unknown fields. *)
172172+170173 val pp : Format.formatter -> t -> unit
171174 (** [pp fmt t] pretty-prints the update. *)
175175+176176+ val jsont : t Jsont.t
177177+ (** [jsont] is the Jsont codec for permission updates. *)
172178end
173179174180(** {1 Permission Context} *)
175181176182module Context : sig
177183 (** Context provided to permission callbacks. *)
178178-184184+185185+ module Unknown : sig
186186+ type t = Jsont.json
187187+ val empty : t
188188+ val is_empty : t -> bool
189189+ val jsont : t Jsont.t
190190+ end
191191+179192 type t = {
180193 suggestions : Update.t list; (** Suggested permission updates *)
194194+ unknown : Unknown.t; (** Unknown fields *)
181195 }
182196 (** The type of permission context. *)
183183-184184- val create : ?suggestions:Update.t list -> unit -> t
185185- (** [create ?suggestions ()] creates a new context.
186186- @param suggestions Optional list of suggested permission updates *)
187187-197197+198198+ val create : ?suggestions:Update.t list -> ?unknown:Unknown.t -> unit -> t
199199+ (** [create ?suggestions ?unknown ()] creates a new context.
200200+ @param suggestions Optional list of suggested permission updates
201201+ @param unknown Optional unknown fields to preserve *)
202202+188203 val suggestions : t -> Update.t list
189204 (** [suggestions t] returns the list of suggested updates. *)
190190-191191- val to_json : t -> value
192192- (** [to_json t] converts a context to JSON. *)
193193-194194- val of_json : value -> t
195195- (** [of_json json] parses a context from JSON.
196196- @raise Invalid_argument if the JSON is not a valid context. *)
197197-205205+206206+ val unknown : t -> Unknown.t
207207+ (** [unknown t] returns the unknown fields. *)
208208+198209 val pp : Format.formatter -> t -> unit
199210 (** [pp fmt t] pretty-prints the context. *)
211211+212212+ val jsont : t Jsont.t
213213+ (** [jsont] is the Jsont codec for permission context. *)
200214end
201215202216(** {1 Permission Results} *)
203217204218module Result : sig
205219 (** Results of permission checks. *)
206206-220220+221221+ module Unknown : sig
222222+ type t = Jsont.json
223223+ val empty : t
224224+ val is_empty : t -> bool
225225+ val jsont : t Jsont.t
226226+ end
227227+207228 type t =
208229 | Allow of {
209209- updated_input : value option; (** Modified tool input *)
230230+ updated_input : Jsont.json option; (** Modified tool input *)
210231 updated_permissions : Update.t list option; (** Permission updates to apply *)
232232+ unknown : Unknown.t; (** Unknown fields *)
211233 }
212234 | Deny of {
213235 message : string; (** Reason for denial *)
214236 interrupt : bool; (** Whether to interrupt execution *)
237237+ unknown : Unknown.t; (** Unknown fields *)
215238 }
216239 (** The type of permission results. *)
217217-218218- val allow : ?updated_input:value -> ?updated_permissions:Update.t list -> unit -> t
219219- (** [allow ?updated_input ?updated_permissions ()] creates an allow result.
240240+241241+ val allow : ?updated_input:Jsont.json -> ?updated_permissions:Update.t list -> ?unknown:Unknown.t -> unit -> t
242242+ (** [allow ?updated_input ?updated_permissions ?unknown ()] creates an allow result.
220243 @param updated_input Optional modified tool input
221221- @param updated_permissions Optional permission updates to apply *)
222222-223223- val deny : message:string -> interrupt:bool -> t
224224- (** [deny ~message ~interrupt] creates a deny result.
244244+ @param updated_permissions Optional permission updates to apply
245245+ @param unknown Optional unknown fields to preserve *)
246246+247247+ val deny : message:string -> interrupt:bool -> ?unknown:Unknown.t -> unit -> t
248248+ (** [deny ~message ~interrupt ?unknown ()] creates a deny result.
225249 @param message The reason for denying permission
226226- @param interrupt Whether to interrupt further execution *)
227227-228228- val to_json : t -> value
229229- (** [to_json t] converts a result to JSON. *)
230230-231231- val of_json : value -> t
232232- (** [of_json json] parses a result from JSON.
233233- @raise Invalid_argument if the JSON is not a valid result. *)
234234-250250+ @param interrupt Whether to interrupt further execution
251251+ @param unknown Optional unknown fields to preserve *)
252252+235253 val pp : Format.formatter -> t -> unit
236254 (** [pp fmt t] pretty-prints the result. *)
255255+256256+ val jsont : t Jsont.t
257257+ (** [jsont] is the Jsont codec for permission results. *)
237258end
238259239260(** {1 Permission Callbacks} *)
240261241241-type callback =
242242- tool_name:string ->
243243- input:value ->
244244- context:Context.t ->
262262+type callback =
263263+ tool_name:string ->
264264+ input:Jsont.json ->
265265+ context:Context.t ->
245266 Result.t
246267(** The type of permission callbacks. Callbacks are invoked when Claude
247268 attempts to use a tool, allowing custom permission logic. *)
+288-256
claudeio/lib/sdk_control.ml
···11-open Ezjsonm
22-31let src = Logs.Src.create "claude.sdk_control" ~doc:"Claude SDK control protocol"
42module Log = (val Logs.src_log src : Logs.LOG)
5366-module JU = Json_utils
44+module Request = struct
55+ module Unknown = struct
66+ type t = Jsont.json
77+ let empty = Jsont.Object ([], Jsont.Meta.none)
88+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
99+ let jsont = Jsont.json
1010+ end
71188-module Request = struct
912 type interrupt = {
1013 subtype : [`Interrupt];
1414+ unknown : Unknown.t;
1115 }
1212-1616+1317 type permission = {
1418 subtype : [`Can_use_tool];
1519 tool_name : string;
1616- input : value;
2020+ input : Jsont.json;
1721 permission_suggestions : Permissions.Update.t list option;
1822 blocked_path : string option;
2323+ unknown : Unknown.t;
1924 }
2020-2525+2126 type initialize = {
2227 subtype : [`Initialize];
2323- hooks : (string * value) list option;
2828+ hooks : (string * Jsont.json) list option;
2929+ unknown : Unknown.t;
2430 }
2525-3131+2632 type set_permission_mode = {
2733 subtype : [`Set_permission_mode];
2834 mode : Permissions.Mode.t;
3535+ unknown : Unknown.t;
2936 }
3030-3737+3138 type hook_callback = {
3239 subtype : [`Hook_callback];
3340 callback_id : string;
3434- input : value;
4141+ input : Jsont.json;
3542 tool_use_id : string option;
4343+ unknown : Unknown.t;
3644 }
3737-4545+3846 type mcp_message = {
3947 subtype : [`Mcp_message];
4048 server_name : string;
4141- message : value;
4949+ message : Jsont.json;
5050+ unknown : Unknown.t;
4251 }
43524453 type set_model = {
4554 subtype : [`Set_model];
4655 model : string;
5656+ unknown : Unknown.t;
4757 }
48584959 type get_server_info = {
5060 subtype : [`Get_server_info];
6161+ unknown : Unknown.t;
5162 }
52635364 type t =
···6071 | Set_model of set_model
6172 | Get_server_info of get_server_info
62736363- let interrupt () = Interrupt { subtype = `Interrupt }
6464-6565- let permission ~tool_name ~input ?permission_suggestions ?blocked_path () =
7474+ let interrupt ?(unknown = Unknown.empty) () =
7575+ Interrupt { subtype = `Interrupt; unknown }
7676+7777+ let permission ~tool_name ~input ?permission_suggestions ?blocked_path ?(unknown = Unknown.empty) () =
6678 Permission {
6779 subtype = `Can_use_tool;
6880 tool_name;
6981 input;
7082 permission_suggestions;
7183 blocked_path;
8484+ unknown;
7285 }
7373-7474- let initialize ?hooks () =
7575- Initialize { subtype = `Initialize; hooks }
7676-7777- let set_permission_mode ~mode =
7878- Set_permission_mode { subtype = `Set_permission_mode; mode }
7979-8080- let hook_callback ~callback_id ~input ?tool_use_id () =
8686+8787+ let initialize ?hooks ?(unknown = Unknown.empty) () =
8888+ Initialize { subtype = `Initialize; hooks; unknown }
8989+9090+ let set_permission_mode ~mode ?(unknown = Unknown.empty) () =
9191+ Set_permission_mode { subtype = `Set_permission_mode; mode; unknown }
9292+9393+ let hook_callback ~callback_id ~input ?tool_use_id ?(unknown = Unknown.empty) () =
8194 Hook_callback {
8295 subtype = `Hook_callback;
8396 callback_id;
8497 input;
8598 tool_use_id;
9999+ unknown;
86100 }
8787-8888- let mcp_message ~server_name ~message =
101101+102102+ let mcp_message ~server_name ~message ?(unknown = Unknown.empty) () =
89103 Mcp_message {
90104 subtype = `Mcp_message;
91105 server_name;
92106 message;
107107+ unknown;
93108 }
941099595- let set_model ~model =
9696- Set_model { subtype = `Set_model; model }
110110+ let set_model ~model ?(unknown = Unknown.empty) () =
111111+ Set_model { subtype = `Set_model; model; unknown }
971129898- let get_server_info () =
9999- Get_server_info { subtype = `Get_server_info }
113113+ let get_server_info ?(unknown = Unknown.empty) () =
114114+ Get_server_info { subtype = `Get_server_info; unknown }
100115101101- let to_json = function
102102- | Interrupt _ ->
103103- `O [("subtype", `String "interrupt")]
104104- | Permission p ->
105105- let fields = [
106106- ("subtype", `String "can_use_tool");
107107- ("tool_name", `String p.tool_name);
108108- ("input", p.input);
109109- ] in
110110- let fields = match p.permission_suggestions with
111111- | Some suggestions ->
112112- ("permission_suggestions",
113113- `A (List.map Permissions.Update.to_json suggestions)) :: fields
114114- | None -> fields
115115- in
116116- let fields = match p.blocked_path with
117117- | Some path -> ("blocked_path", `String path) :: fields
118118- | None -> fields
119119- in
120120- `O fields
121121- | Initialize i ->
122122- let fields = [("subtype", `String "initialize")] in
123123- let fields = match i.hooks with
124124- | Some hooks ->
125125- ("hooks", `O hooks) :: fields
126126- | None -> fields
127127- in
128128- `O fields
129129- | Set_permission_mode s ->
130130- `O [
131131- ("subtype", `String "set_permission_mode");
132132- ("mode", Permissions.Mode.to_json s.mode);
133133- ]
134134- | Hook_callback h ->
135135- let fields = [
136136- ("subtype", `String "hook_callback");
137137- ("callback_id", `String h.callback_id);
138138- ("input", h.input);
139139- ] in
140140- let fields = match h.tool_use_id with
141141- | Some id -> ("tool_use_id", `String id) :: fields
142142- | None -> fields
143143- in
144144- `O fields
145145- | Mcp_message m ->
146146- `O [
147147- ("subtype", `String "mcp_message");
148148- ("server_name", `String m.server_name);
149149- ("message", m.message);
150150- ]
151151- | Set_model s ->
152152- `O [
153153- ("subtype", `String "set_model");
154154- ("model", `String s.model);
155155- ]
156156- | Get_server_info _ ->
157157- `O [("subtype", `String "get_server_info")]
116116+ (* Individual record codecs *)
117117+ let interrupt_jsont : interrupt Jsont.t =
118118+ let make (unknown : Unknown.t) : interrupt = { subtype = `Interrupt; unknown } in
119119+ Jsont.Object.map ~kind:"Interrupt" make
120120+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : interrupt) -> r.unknown)
121121+ |> Jsont.Object.finish
158122159159- let of_json = function
160160- | `O fields ->
161161- let subtype = JU.assoc_string "subtype" fields in
162162- (match subtype with
163163- | "interrupt" ->
164164- Interrupt { subtype = `Interrupt }
165165- | "can_use_tool" ->
166166- let tool_name = JU.assoc_string "tool_name" fields in
167167- let input = List.assoc "input" fields in
168168- let permission_suggestions =
169169- match List.assoc_opt "permission_suggestions" fields with
170170- | Some (`A lst) ->
171171- Some (List.map Permissions.Update.of_json lst)
172172- | _ -> None
173173- in
174174- let blocked_path = JU.assoc_string_opt "blocked_path" fields in
175175- Permission {
176176- subtype = `Can_use_tool;
177177- tool_name;
178178- input;
179179- permission_suggestions;
180180- blocked_path;
181181- }
182182- | "initialize" ->
183183- let hooks =
184184- match List.assoc_opt "hooks" fields with
185185- | Some (`O hooks) -> Some hooks
186186- | _ -> None
187187- in
188188- Initialize { subtype = `Initialize; hooks }
189189- | "set_permission_mode" ->
190190- let mode = List.assoc "mode" fields |> Permissions.Mode.of_json in
191191- Set_permission_mode { subtype = `Set_permission_mode; mode }
192192- | "hook_callback" ->
193193- let callback_id = JU.assoc_string "callback_id" fields in
194194- let input = List.assoc "input" fields in
195195- let tool_use_id = JU.assoc_string_opt "tool_use_id" fields in
196196- Hook_callback {
197197- subtype = `Hook_callback;
198198- callback_id;
199199- input;
200200- tool_use_id;
201201- }
202202- | "mcp_message" ->
203203- let server_name = JU.assoc_string "server_name" fields in
204204- let message = List.assoc "message" fields in
205205- Mcp_message {
206206- subtype = `Mcp_message;
207207- server_name;
208208- message;
209209- }
210210- | "set_model" ->
211211- let model = JU.assoc_string "model" fields in
212212- Set_model { subtype = `Set_model; model }
213213- | "get_server_info" ->
214214- Get_server_info { subtype = `Get_server_info }
215215- | _ -> raise (Invalid_argument ("Unknown request subtype: " ^ subtype)))
216216- | _ -> raise (Invalid_argument "Request.of_json: expected object")
123123+ let permission_jsont : permission Jsont.t =
124124+ let make tool_name input permission_suggestions blocked_path (unknown : Unknown.t) : permission =
125125+ { subtype = `Can_use_tool; tool_name; input; permission_suggestions; blocked_path; unknown }
126126+ in
127127+ Jsont.Object.map ~kind:"Permission" make
128128+ |> Jsont.Object.mem "tool_name" Jsont.string ~enc:(fun (r : permission) -> r.tool_name)
129129+ |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : permission) -> r.input)
130130+ |> Jsont.Object.opt_mem "permission_suggestions" (Jsont.list Permissions.Update.jsont) ~enc:(fun (r : permission) -> r.permission_suggestions)
131131+ |> Jsont.Object.opt_mem "blocked_path" Jsont.string ~enc:(fun (r : permission) -> r.blocked_path)
132132+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : permission) -> r.unknown)
133133+ |> Jsont.Object.finish
134134+135135+ let initialize_jsont : initialize Jsont.t =
136136+ (* The hooks field is an object with string keys and json values *)
137137+ let hooks_map_jsont = Jsont.Object.as_string_map Jsont.json in
138138+ let module StringMap = Map.Make(String) in
139139+ let hooks_jsont = Jsont.map
140140+ ~dec:(fun m -> StringMap.bindings m)
141141+ ~enc:(fun l -> StringMap.of_seq (List.to_seq l))
142142+ hooks_map_jsont
143143+ in
144144+ let make hooks (unknown : Unknown.t) : initialize = { subtype = `Initialize; hooks; unknown } in
145145+ Jsont.Object.map ~kind:"Initialize" make
146146+ |> Jsont.Object.opt_mem "hooks" hooks_jsont ~enc:(fun (r : initialize) -> r.hooks)
147147+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : initialize) -> r.unknown)
148148+ |> Jsont.Object.finish
149149+150150+ let set_permission_mode_jsont : set_permission_mode Jsont.t =
151151+ let make mode (unknown : Unknown.t) : set_permission_mode = { subtype = `Set_permission_mode; mode; unknown } in
152152+ Jsont.Object.map ~kind:"SetPermissionMode" make
153153+ |> Jsont.Object.mem "mode" Permissions.Mode.jsont ~enc:(fun (r : set_permission_mode) -> r.mode)
154154+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : set_permission_mode) -> r.unknown)
155155+ |> Jsont.Object.finish
156156+157157+ let hook_callback_jsont : hook_callback Jsont.t =
158158+ let make callback_id input tool_use_id (unknown : Unknown.t) : hook_callback =
159159+ { subtype = `Hook_callback; callback_id; input; tool_use_id; unknown }
160160+ in
161161+ Jsont.Object.map ~kind:"HookCallback" make
162162+ |> Jsont.Object.mem "callback_id" Jsont.string ~enc:(fun (r : hook_callback) -> r.callback_id)
163163+ |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : hook_callback) -> r.input)
164164+ |> Jsont.Object.opt_mem "tool_use_id" Jsont.string ~enc:(fun (r : hook_callback) -> r.tool_use_id)
165165+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : hook_callback) -> r.unknown)
166166+ |> Jsont.Object.finish
167167+168168+ let mcp_message_jsont : mcp_message Jsont.t =
169169+ let make server_name message (unknown : Unknown.t) : mcp_message =
170170+ { subtype = `Mcp_message; server_name; message; unknown }
171171+ in
172172+ Jsont.Object.map ~kind:"McpMessage" make
173173+ |> Jsont.Object.mem "server_name" Jsont.string ~enc:(fun (r : mcp_message) -> r.server_name)
174174+ |> Jsont.Object.mem "message" Jsont.json ~enc:(fun (r : mcp_message) -> r.message)
175175+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : mcp_message) -> r.unknown)
176176+ |> Jsont.Object.finish
177177+178178+ let set_model_jsont : set_model Jsont.t =
179179+ let make model (unknown : Unknown.t) : set_model = { subtype = `Set_model; model; unknown } in
180180+ Jsont.Object.map ~kind:"SetModel" make
181181+ |> Jsont.Object.mem "model" Jsont.string ~enc:(fun (r : set_model) -> r.model)
182182+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : set_model) -> r.unknown)
183183+ |> Jsont.Object.finish
184184+185185+ let get_server_info_jsont : get_server_info Jsont.t =
186186+ let make (unknown : Unknown.t) : get_server_info = { subtype = `Get_server_info; unknown } in
187187+ Jsont.Object.map ~kind:"GetServerInfo" make
188188+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : get_server_info) -> r.unknown)
189189+ |> Jsont.Object.finish
190190+191191+ (* Main variant codec using subtype discriminator *)
192192+ let jsont : t Jsont.t =
193193+ let case_interrupt = Jsont.Object.Case.map "interrupt" interrupt_jsont ~dec:(fun v -> Interrupt v) in
194194+ let case_permission = Jsont.Object.Case.map "can_use_tool" permission_jsont ~dec:(fun v -> Permission v) in
195195+ let case_initialize = Jsont.Object.Case.map "initialize" initialize_jsont ~dec:(fun v -> Initialize v) in
196196+ let case_set_permission_mode = Jsont.Object.Case.map "set_permission_mode" set_permission_mode_jsont ~dec:(fun v -> Set_permission_mode v) in
197197+ let case_hook_callback = Jsont.Object.Case.map "hook_callback" hook_callback_jsont ~dec:(fun v -> Hook_callback v) in
198198+ let case_mcp_message = Jsont.Object.Case.map "mcp_message" mcp_message_jsont ~dec:(fun v -> Mcp_message v) in
199199+ let case_set_model = Jsont.Object.Case.map "set_model" set_model_jsont ~dec:(fun v -> Set_model v) in
200200+ let case_get_server_info = Jsont.Object.Case.map "get_server_info" get_server_info_jsont ~dec:(fun v -> Get_server_info v) in
201201+202202+ let enc_case = function
203203+ | Interrupt v -> Jsont.Object.Case.value case_interrupt v
204204+ | Permission v -> Jsont.Object.Case.value case_permission v
205205+ | Initialize v -> Jsont.Object.Case.value case_initialize v
206206+ | Set_permission_mode v -> Jsont.Object.Case.value case_set_permission_mode v
207207+ | Hook_callback v -> Jsont.Object.Case.value case_hook_callback v
208208+ | Mcp_message v -> Jsont.Object.Case.value case_mcp_message v
209209+ | Set_model v -> Jsont.Object.Case.value case_set_model v
210210+ | Get_server_info v -> Jsont.Object.Case.value case_get_server_info v
211211+ in
212212+213213+ let cases = Jsont.Object.Case.[
214214+ make case_interrupt;
215215+ make case_permission;
216216+ make case_initialize;
217217+ make case_set_permission_mode;
218218+ make case_hook_callback;
219219+ make case_mcp_message;
220220+ make case_set_model;
221221+ make case_get_server_info;
222222+ ] in
223223+224224+ Jsont.Object.map ~kind:"Request" Fun.id
225225+ |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
226226+ ~tag_to_string:Fun.id ~tag_compare:String.compare
227227+ |> Jsont.Object.finish
217228218229 let pp fmt = function
219230 | Interrupt _ ->
···240251end
241252242253module Response = struct
254254+ module Unknown = struct
255255+ type t = Jsont.json
256256+ let empty = Jsont.Object ([], Jsont.Meta.none)
257257+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
258258+ let jsont = Jsont.json
259259+ end
260260+243261 type success = {
244262 subtype : [`Success];
245263 request_id : string;
246246- response : value option;
264264+ response : Jsont.json option;
265265+ unknown : Unknown.t;
247266 }
248248-267267+249268 type error = {
250269 subtype : [`Error];
251270 request_id : string;
252271 error : string;
272272+ unknown : Unknown.t;
253273 }
254254-274274+255275 type t =
256276 | Success of success
257277 | Error of error
258258-259259- let success ~request_id ?response () =
278278+279279+ let success ~request_id ?response ?(unknown = Unknown.empty) () =
260280 Success {
261281 subtype = `Success;
262282 request_id;
263283 response;
284284+ unknown;
264285 }
265265-266266- let error ~request_id ~error =
286286+287287+ let error ~request_id ~error ?(unknown = Unknown.empty) () =
267288 Error {
268289 subtype = `Error;
269290 request_id;
270291 error;
292292+ unknown;
271293 }
272272-273273- let to_json = function
274274- | Success s ->
275275- let fields = [
276276- ("subtype", `String "success");
277277- ("request_id", `String s.request_id);
278278- ] in
279279- let fields = match s.response with
280280- | Some resp -> ("response", resp) :: fields
281281- | None -> fields
282282- in
283283- `O fields
284284- | Error e ->
285285- `O [
286286- ("subtype", `String "error");
287287- ("request_id", `String e.request_id);
288288- ("error", `String e.error);
289289- ]
290290-291291- let of_json = function
292292- | `O fields ->
293293- let subtype = JU.assoc_string "subtype" fields in
294294- let request_id = JU.assoc_string "request_id" fields in
295295- (match subtype with
296296- | "success" ->
297297- let response = List.assoc_opt "response" fields in
298298- Success {
299299- subtype = `Success;
300300- request_id;
301301- response;
302302- }
303303- | "error" ->
304304- let error = JU.assoc_string "error" fields in
305305- Error {
306306- subtype = `Error;
307307- request_id;
308308- error;
309309- }
310310- | _ -> raise (Invalid_argument ("Unknown response subtype: " ^ subtype)))
311311- | _ -> raise (Invalid_argument "Response.of_json: expected object")
294294+295295+ (* Individual record codecs *)
296296+ let success_jsont : success Jsont.t =
297297+ let make request_id response (unknown : Unknown.t) : success =
298298+ { subtype = `Success; request_id; response; unknown }
299299+ in
300300+ Jsont.Object.map ~kind:"Success" make
301301+ |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun (r : success) -> r.request_id)
302302+ |> Jsont.Object.opt_mem "response" Jsont.json ~enc:(fun (r : success) -> r.response)
303303+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : success) -> r.unknown)
304304+ |> Jsont.Object.finish
305305+306306+ let error_jsont : error Jsont.t =
307307+ let make request_id error (unknown : Unknown.t) : error =
308308+ { subtype = `Error; request_id; error; unknown }
309309+ in
310310+ Jsont.Object.map ~kind:"Error" make
311311+ |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun (r : error) -> r.request_id)
312312+ |> Jsont.Object.mem "error" Jsont.string ~enc:(fun (r : error) -> r.error)
313313+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : error) -> r.unknown)
314314+ |> Jsont.Object.finish
315315+316316+ (* Main variant codec using subtype discriminator *)
317317+ let jsont : t Jsont.t =
318318+ let case_success = Jsont.Object.Case.map "success" success_jsont ~dec:(fun v -> Success v) in
319319+ let case_error = Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v) in
320320+321321+ let enc_case = function
322322+ | Success v -> Jsont.Object.Case.value case_success v
323323+ | Error v -> Jsont.Object.Case.value case_error v
324324+ in
325325+326326+ let cases = Jsont.Object.Case.[
327327+ make case_success;
328328+ make case_error;
329329+ ] in
330330+331331+ Jsont.Object.map ~kind:"Response" Fun.id
332332+ |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
333333+ ~tag_to_string:Fun.id ~tag_compare:String.compare
334334+ |> Jsont.Object.finish
312335313336 let pp fmt = function
314337 | Success s ->
···319342 e.request_id e.error
320343end
321344345345+module Unknown = struct
346346+ type t = Jsont.json
347347+ let empty = Jsont.Object ([], Jsont.Meta.none)
348348+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
349349+ let jsont = Jsont.json
350350+end
351351+322352type control_request = {
323353 type_ : [`Control_request];
324354 request_id : string;
325355 request : Request.t;
356356+ unknown : Unknown.t;
326357}
327358328359type control_response = {
329360 type_ : [`Control_response];
330361 response : Response.t;
362362+ unknown : Unknown.t;
331363}
332364333365type t =
334366 | Request of control_request
335367 | Response of control_response
336368337337-let create_request ~request_id ~request =
369369+let create_request ~request_id ~request ?(unknown = Unknown.empty) () =
338370 Request {
339371 type_ = `Control_request;
340372 request_id;
341373 request;
374374+ unknown;
342375 }
343376344344-let create_response ~response =
377377+let create_response ~response ?(unknown = Unknown.empty) () =
345378 Response {
346379 type_ = `Control_response;
347380 response;
381381+ unknown;
348382 }
349383350350-let to_json = function
351351- | Request r ->
352352- `O [
353353- ("type", `String "control_request");
354354- ("request_id", `String r.request_id);
355355- ("request", Request.to_json r.request);
356356- ]
357357- | Response r ->
358358- `O [
359359- ("type", `String "control_response");
360360- ("response", Response.to_json r.response);
361361- ]
384384+(* Individual record codecs *)
385385+let control_request_jsont : control_request Jsont.t =
386386+ let make request_id request (unknown : Unknown.t) : control_request =
387387+ { type_ = `Control_request; request_id; request; unknown }
388388+ in
389389+ Jsont.Object.map ~kind:"ControlRequest" make
390390+ |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun (r : control_request) -> r.request_id)
391391+ |> Jsont.Object.mem "request" Request.jsont ~enc:(fun (r : control_request) -> r.request)
392392+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : control_request) -> r.unknown)
393393+ |> Jsont.Object.finish
362394363363-let of_json = function
364364- | `O fields ->
365365- let type_ = JU.assoc_string "type" fields in
366366- (match type_ with
367367- | "control_request" ->
368368- let request_id = JU.assoc_string "request_id" fields in
369369- let request = List.assoc "request" fields |> Request.of_json in
370370- Request {
371371- type_ = `Control_request;
372372- request_id;
373373- request;
374374- }
375375- | "control_response" ->
376376- let response = List.assoc "response" fields |> Response.of_json in
377377- Response {
378378- type_ = `Control_response;
379379- response;
380380- }
381381- | _ -> raise (Invalid_argument ("Unknown control type: " ^ type_)))
382382- | _ -> raise (Invalid_argument "of_json: expected object")
395395+let control_response_jsont : control_response Jsont.t =
396396+ let make response (unknown : Unknown.t) : control_response =
397397+ { type_ = `Control_response; response; unknown }
398398+ in
399399+ Jsont.Object.map ~kind:"ControlResponse" make
400400+ |> Jsont.Object.mem "response" Response.jsont ~enc:(fun (r : control_response) -> r.response)
401401+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : control_response) -> r.unknown)
402402+ |> Jsont.Object.finish
403403+404404+(* Main variant codec using type discriminator *)
405405+let jsont : t Jsont.t =
406406+ let case_request = Jsont.Object.Case.map "control_request" control_request_jsont ~dec:(fun v -> Request v) in
407407+ let case_response = Jsont.Object.Case.map "control_response" control_response_jsont ~dec:(fun v -> Response v) in
408408+409409+ let enc_case = function
410410+ | Request v -> Jsont.Object.Case.value case_request v
411411+ | Response v -> Jsont.Object.Case.value case_response v
412412+ in
413413+414414+ let cases = Jsont.Object.Case.[
415415+ make case_request;
416416+ make case_response;
417417+ ] in
418418+419419+ Jsont.Object.map ~kind:"Control" Fun.id
420420+ |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
421421+ ~tag_to_string:Fun.id ~tag_compare:String.compare
422422+ |> Jsont.Object.finish
383423384424let pp fmt = function
385425 | Request r ->
···397437398438(** Server information *)
399439module Server_info = struct
440440+ module Unknown = struct
441441+ type t = Jsont.json
442442+ let empty = Jsont.Object ([], Jsont.Meta.none)
443443+ let is_empty = function Jsont.Object ([], _) -> true | _ -> false
444444+ let jsont = Jsont.json
445445+ end
446446+400447 type t = {
401448 version : string;
402449 capabilities : string list;
403450 commands : string list;
404451 output_styles : string list;
452452+ unknown : Unknown.t;
405453 }
406454407407- let create ~version ~capabilities ~commands ~output_styles =
408408- { version; capabilities; commands; output_styles }
455455+ let create ~version ~capabilities ~commands ~output_styles ?(unknown = Unknown.empty) () =
456456+ { version; capabilities; commands; output_styles; unknown }
409457410458 let version t = t.version
411459 let capabilities t = t.capabilities
412460 let commands t = t.commands
413461 let output_styles t = t.output_styles
414414-415415- let of_json = function
416416- | `O fields ->
417417- let version = JU.assoc_string "version" fields in
418418- let capabilities =
419419- match List.assoc_opt "capabilities" fields with
420420- | Some (`A lst) -> List.map Ezjsonm.get_string lst
421421- | _ -> []
422422- in
423423- let commands =
424424- match List.assoc_opt "commands" fields with
425425- | Some (`A lst) -> List.map Ezjsonm.get_string lst
426426- | _ -> []
427427- in
428428- let output_styles =
429429- match List.assoc_opt "outputStyles" fields with
430430- | Some (`A lst) -> List.map Ezjsonm.get_string lst
431431- | _ -> []
432432- in
433433- { version; capabilities; commands; output_styles }
434434- | _ -> raise (Invalid_argument "Server_info.of_json: expected object")
462462+ let unknown t = t.unknown
435463436436- let to_json t =
437437- `O [
438438- ("version", `String t.version);
439439- ("capabilities", `A (List.map (fun s -> `String s) t.capabilities));
440440- ("commands", `A (List.map (fun s -> `String s) t.commands));
441441- ("outputStyles", `A (List.map (fun s -> `String s) t.output_styles));
442442- ]
464464+ let jsont : t Jsont.t =
465465+ let make version capabilities commands output_styles (unknown : Unknown.t) : t =
466466+ { version; capabilities; commands; output_styles; unknown }
467467+ in
468468+ Jsont.Object.map ~kind:"ServerInfo" make
469469+ |> Jsont.Object.mem "version" Jsont.string ~enc:(fun (r : t) -> r.version)
470470+ |> Jsont.Object.mem "capabilities" (Jsont.list Jsont.string) ~enc:(fun (r : t) -> r.capabilities) ~dec_absent:[]
471471+ |> Jsont.Object.mem "commands" (Jsont.list Jsont.string) ~enc:(fun (r : t) -> r.commands) ~dec_absent:[]
472472+ |> Jsont.Object.mem "outputStyles" (Jsont.list Jsont.string) ~enc:(fun (r : t) -> r.output_styles) ~dec_absent:[]
473473+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : t) -> r.unknown)
474474+ |> Jsont.Object.finish
443475444476 let pp fmt t =
445477 Fmt.pf fmt "@[<2>ServerInfo@ { version = %S;@ capabilities = [%a];@ commands = [%a];@ output_styles = [%a] }@]"
+117-82
claudeio/lib/sdk_control.mli
···5151 See {!Client.set_permission_mode}, {!Client.set_model}, and
5252 {!Client.get_server_info} for high-level APIs that use this protocol. *)
53535454-open Ezjsonm
5555-5654(** The log source for SDK control operations *)
5755val src : Logs.Src.t
5856···60586159module Request : sig
6260 (** SDK control request types. *)
6363-6161+6262+ module Unknown : sig
6363+ type t = Jsont.json
6464+ val empty : t
6565+ val is_empty : t -> bool
6666+ val jsont : t Jsont.t
6767+ end
6868+6469 type interrupt = {
6570 subtype : [`Interrupt];
7171+ unknown : Unknown.t;
6672 }
6773 (** Interrupt request to stop execution. *)
6868-7474+6975 type permission = {
7076 subtype : [`Can_use_tool];
7177 tool_name : string;
7272- input : value;
7878+ input : Jsont.json;
7379 permission_suggestions : Permissions.Update.t list option;
7480 blocked_path : string option;
8181+ unknown : Unknown.t;
7582 }
7683 (** Permission request for tool usage. *)
7777-8484+7885 type initialize = {
7986 subtype : [`Initialize];
8080- hooks : (string * value) list option; (* Hook event to configuration *)
8787+ hooks : (string * Jsont.json) list option; (* Hook event to configuration *)
8888+ unknown : Unknown.t;
8189 }
8290 (** Initialize request with optional hook configuration. *)
8383-9191+8492 type set_permission_mode = {
8593 subtype : [`Set_permission_mode];
8694 mode : Permissions.Mode.t;
9595+ unknown : Unknown.t;
8796 }
8897 (** Request to change permission mode. *)
8989-9898+9099 type hook_callback = {
91100 subtype : [`Hook_callback];
92101 callback_id : string;
9393- input : value;
102102+ input : Jsont.json;
94103 tool_use_id : string option;
104104+ unknown : Unknown.t;
95105 }
96106 (** Hook callback request. *)
9797-107107+98108 type mcp_message = {
99109 subtype : [`Mcp_message];
100110 server_name : string;
101101- message : value;
111111+ message : Jsont.json;
112112+ unknown : Unknown.t;
102113 }
103114 (** MCP server message request. *)
104115105116 type set_model = {
106117 subtype : [`Set_model];
107118 model : string;
119119+ unknown : Unknown.t;
108120 }
109121 (** Request to change the AI model. *)
110122111123 type get_server_info = {
112124 subtype : [`Get_server_info];
125125+ unknown : Unknown.t;
113126 }
114127 (** Request to get server information. *)
115128···123136 | Set_model of set_model
124137 | Get_server_info of get_server_info
125138 (** The type of SDK control requests. *)
126126-127127- val interrupt : unit -> t
128128- (** [interrupt ()] creates an interrupt request. *)
129129-130130- val permission :
131131- tool_name:string ->
132132- input:value ->
133133- ?permission_suggestions:Permissions.Update.t list ->
134134- ?blocked_path:string ->
139139+140140+ val interrupt : ?unknown:Unknown.t -> unit -> t
141141+ (** [interrupt ?unknown ()] creates an interrupt request. *)
142142+143143+ val permission :
144144+ tool_name:string ->
145145+ input:Jsont.json ->
146146+ ?permission_suggestions:Permissions.Update.t list ->
147147+ ?blocked_path:string ->
148148+ ?unknown:Unknown.t ->
135149 unit -> t
136136- (** [permission ~tool_name ~input ?permission_suggestions ?blocked_path ()]
150150+ (** [permission ~tool_name ~input ?permission_suggestions ?blocked_path ?unknown ()]
137151 creates a permission request. *)
138138-139139- val initialize : ?hooks:(string * value) list -> unit -> t
140140- (** [initialize ?hooks ()] creates an initialize request. *)
141141-142142- val set_permission_mode : mode:Permissions.Mode.t -> t
143143- (** [set_permission_mode ~mode] creates a permission mode change request. *)
144144-145145- val hook_callback :
146146- callback_id:string ->
147147- input:value ->
148148- ?tool_use_id:string ->
152152+153153+ val initialize : ?hooks:(string * Jsont.json) list -> ?unknown:Unknown.t -> unit -> t
154154+ (** [initialize ?hooks ?unknown ()] creates an initialize request. *)
155155+156156+ val set_permission_mode : mode:Permissions.Mode.t -> ?unknown:Unknown.t -> unit -> t
157157+ (** [set_permission_mode ~mode ?unknown] creates a permission mode change request. *)
158158+159159+ val hook_callback :
160160+ callback_id:string ->
161161+ input:Jsont.json ->
162162+ ?tool_use_id:string ->
163163+ ?unknown:Unknown.t ->
149164 unit -> t
150150- (** [hook_callback ~callback_id ~input ?tool_use_id ()] creates a hook callback request. *)
151151-152152- val mcp_message : server_name:string -> message:value -> t
153153- (** [mcp_message ~server_name ~message] creates an MCP message request. *)
165165+ (** [hook_callback ~callback_id ~input ?tool_use_id ?unknown ()] creates a hook callback request. *)
166166+167167+ val mcp_message : server_name:string -> message:Jsont.json -> ?unknown:Unknown.t -> unit -> t
168168+ (** [mcp_message ~server_name ~message ?unknown] creates an MCP message request. *)
169169+170170+ val set_model : model:string -> ?unknown:Unknown.t -> unit -> t
171171+ (** [set_model ~model ?unknown] creates a model change request. *)
154172155155- val set_model : model:string -> t
156156- (** [set_model ~model] creates a model change request. *)
173173+ val get_server_info : ?unknown:Unknown.t -> unit -> t
174174+ (** [get_server_info ?unknown ()] creates a server info request. *)
157175158158- val get_server_info : unit -> t
159159- (** [get_server_info ()] creates a server info request. *)
176176+ val jsont : t Jsont.t
177177+ (** [jsont] is the jsont codec for requests. *)
160178161161- val to_json : t -> value
162162- (** [to_json t] converts a request to JSON. *)
163163-164164- val of_json : value -> t
165165- (** [of_json json] parses a request from JSON.
166166- @raise Invalid_argument if the JSON is not a valid request. *)
167167-168179 val pp : Format.formatter -> t -> unit
169180 (** [pp fmt t] pretty-prints the request. *)
170181end
···173184174185module Response : sig
175186 (** SDK control response types. *)
176176-187187+188188+ module Unknown : sig
189189+ type t = Jsont.json
190190+ val empty : t
191191+ val is_empty : t -> bool
192192+ val jsont : t Jsont.t
193193+ end
194194+177195 type success = {
178196 subtype : [`Success];
179197 request_id : string;
180180- response : value option;
198198+ response : Jsont.json option;
199199+ unknown : Unknown.t;
181200 }
182201 (** Successful response. *)
183183-202202+184203 type error = {
185204 subtype : [`Error];
186205 request_id : string;
187206 error : string;
207207+ unknown : Unknown.t;
188208 }
189209 (** Error response. *)
190190-210210+191211 type t =
192212 | Success of success
193213 | Error of error
194214 (** The type of SDK control responses. *)
195195-196196- val success : request_id:string -> ?response:value -> unit -> t
197197- (** [success ~request_id ?response ()] creates a success response. *)
198198-199199- val error : request_id:string -> error:string -> t
200200- (** [error ~request_id ~error] creates an error response. *)
201201-202202- val to_json : t -> value
203203- (** [to_json t] converts a response to JSON. *)
204204-205205- val of_json : value -> t
206206- (** [of_json json] parses a response from JSON.
207207- @raise Invalid_argument if the JSON is not a valid response. *)
208208-215215+216216+ val success : request_id:string -> ?response:Jsont.json -> ?unknown:Unknown.t -> unit -> t
217217+ (** [success ~request_id ?response ?unknown ()] creates a success response. *)
218218+219219+ val error : request_id:string -> error:string -> ?unknown:Unknown.t -> unit -> t
220220+ (** [error ~request_id ~error ?unknown] creates an error response. *)
221221+222222+ val jsont : t Jsont.t
223223+ (** [jsont] is the jsont codec for responses. *)
224224+209225 val pp : Format.formatter -> t -> unit
210226 (** [pp fmt t] pretty-prints the response. *)
211227end
212228213229(** {1 Control Messages} *)
214230231231+module Unknown : sig
232232+ type t = Jsont.json
233233+ val empty : t
234234+ val is_empty : t -> bool
235235+ val jsont : t Jsont.t
236236+end
237237+215238type control_request = {
216239 type_ : [`Control_request];
217240 request_id : string;
218241 request : Request.t;
242242+ unknown : Unknown.t;
219243}
220244(** Control request message. *)
221245222246type control_response = {
223247 type_ : [`Control_response];
224248 response : Response.t;
249249+ unknown : Unknown.t;
225250}
226251(** Control response message. *)
227252253253+val control_response_jsont : control_response Jsont.t
254254+(** [control_response_jsont] is the jsont codec for control response messages. *)
255255+228256type t =
229257 | Request of control_request
230258 | Response of control_response
231259(** The type of SDK control messages. *)
232260233233-val create_request : request_id:string -> request:Request.t -> t
234234-(** [create_request ~request_id ~request] creates a control request message. *)
261261+val create_request : request_id:string -> request:Request.t -> ?unknown:Unknown.t -> unit -> t
262262+(** [create_request ~request_id ~request ?unknown ()] creates a control request message. *)
235263236236-val create_response : response:Response.t -> t
237237-(** [create_response ~response] creates a control response message. *)
264264+val create_response : response:Response.t -> ?unknown:Unknown.t -> unit -> t
265265+(** [create_response ~response ?unknown ()] creates a control response message. *)
238266239239-val to_json : t -> value
240240-(** [to_json t] converts a control message to JSON. *)
241241-242242-val of_json : value -> t
243243-(** [of_json json] parses a control message from JSON.
244244- @raise Invalid_argument if the JSON is not a valid control message. *)
267267+val jsont : t Jsont.t
268268+(** [jsont] is the jsont codec for control messages. *)
245269246270val pp : Format.formatter -> t -> unit
247271(** [pp fmt t] pretty-prints the control message. *)
···282306module Server_info : sig
283307 (** Server information and capabilities. *)
284308309309+ module Unknown : sig
310310+ type t = Jsont.json
311311+ val empty : t
312312+ val is_empty : t -> bool
313313+ val jsont : t Jsont.t
314314+ end
315315+285316 type t = {
286317 version : string;
287318 (** Server version string (e.g., "2.0.0") *)
···294325295326 output_styles : string list;
296327 (** Supported output formats (e.g., "json", "stream-json") *)
328328+329329+ unknown : Unknown.t;
330330+ (** Unknown fields for forward compatibility *)
297331 }
298332 (** Server metadata and capabilities.
299333···304338 capabilities:string list ->
305339 commands:string list ->
306340 output_styles:string list ->
341341+ ?unknown:Unknown.t ->
342342+ unit ->
307343 t
308308- (** [create ~version ~capabilities ~commands ~output_styles] creates server info. *)
344344+ (** [create ~version ~capabilities ~commands ~output_styles ?unknown ()] creates server info. *)
309345310346 val version : t -> string
311347 (** [version t] returns the server version. *)
···319355 val output_styles : t -> string list
320356 (** [output_styles t] returns available output styles. *)
321357322322- val of_json : value -> t
323323- (** [of_json json] parses server info from JSON.
324324- @raise Invalid_argument if the JSON is not valid server info. *)
358358+ val unknown : t -> Unknown.t
359359+ (** [unknown t] returns the unknown fields. *)
325360326326- val to_json : t -> value
327327- (** [to_json t] converts server info to JSON. *)
361361+ val jsont : t Jsont.t
362362+ (** [jsont] is the jsont codec for server info. *)
328363329364 val pp : Format.formatter -> t -> unit
330365 (** [pp fmt t] pretty-prints the server info. *)
+49
claudeio/lib/structured_output.ml
···11+let src = Logs.Src.create "claude.structured_output" ~doc:"Structured output"
22+module Log = (val Logs.src_log src : Logs.LOG)
33+44+type t = {
55+ json_schema : Jsont.json;
66+}
77+88+let json_to_string json =
99+ match Jsont_bytesrw.encode_string' Jsont.json json with
1010+ | Ok str -> str
1111+ | Error err -> failwith (Jsont.Error.to_string err)
1212+1313+let of_json_schema schema =
1414+ Log.debug (fun m -> m "Created output format from JSON schema: %s"
1515+ (json_to_string schema));
1616+ { json_schema = schema }
1717+1818+let json_schema t = t.json_schema
1919+2020+(* Codec for serializing structured output format *)
2121+let jsont : t Jsont.t =
2222+ Jsont.Object.map ~kind:"StructuredOutput"
2323+ (fun json_schema -> {json_schema})
2424+ |> Jsont.Object.mem "jsonSchema" Jsont.json ~enc:(fun t -> t.json_schema)
2525+ |> Jsont.Object.finish
2626+2727+let to_json t =
2828+ match Jsont.Json.encode jsont t with
2929+ | Ok json -> json
3030+ | Error msg -> failwith ("Structured_output.to_json: " ^ msg)
3131+3232+let of_json json =
3333+ match Jsont.Json.decode jsont json with
3434+ | Ok t -> t
3535+ | Error msg -> raise (Invalid_argument ("Structured_output.of_json: " ^ msg))
3636+3737+let pp fmt t =
3838+ let schema_str =
3939+ match Jsont_bytesrw.encode_string' ~format:Jsont.Minify Jsont.json t.json_schema with
4040+ | Ok s -> s
4141+ | Error err -> Jsont.Error.to_string err
4242+ in
4343+ let truncated =
4444+ if String.length schema_str > 100 then
4545+ String.sub schema_str 0 97 ^ "..."
4646+ else
4747+ schema_str
4848+ in
4949+ Fmt.pf fmt "@[<2>StructuredOutput { schema = %s }@]" truncated
+171
claudeio/lib/structured_output.mli
···11+(** Structured output configuration using JSON Schema.
22+33+ This module provides structured output support for Claude, allowing you to
44+ specify the expected output format using JSON schemas. When a structured
55+ output format is configured, Claude will return its response in the
66+ specified JSON format, validated against your schema.
77+88+ {2 Overview}
99+1010+ Structured outputs ensure that Claude's responses conform to a specific
1111+ JSON schema, making it easier to parse and use the results programmatically.
1212+ This is particularly useful for:
1313+1414+ - Extracting structured data from unstructured text
1515+ - Building APIs that require consistent JSON responses
1616+ - Integrating Claude into data pipelines
1717+ - Ensuring type-safe parsing of Claude's outputs
1818+1919+ {2 Creating Output Formats}
2020+2121+ Use {!of_json_schema} to specify a JSON Schema as a {!Jsont.json} value:
2222+ {[
2323+ let meta = Jsont.Meta.none in
2424+ let schema = Jsont.Object ([
2525+ (("type", meta), Jsont.String ("object", meta));
2626+ (("properties", meta), Jsont.Object ([
2727+ (("name", meta), Jsont.Object ([
2828+ (("type", meta), Jsont.String ("string", meta))
2929+ ], meta));
3030+ (("age", meta), Jsont.Object ([
3131+ (("type", meta), Jsont.String ("integer", meta))
3232+ ], meta));
3333+ ], meta));
3434+ (("required", meta), Jsont.Array ([
3535+ Jsont.String ("name", meta);
3636+ Jsont.String ("age", meta)
3737+ ], meta));
3838+ ], meta) in
3939+4040+ let format = Structured_output.of_json_schema schema
4141+ ]}
4242+4343+ {3 Helper Functions for Building Schemas}
4444+4545+ For complex schemas, you can use helper functions to make construction easier:
4646+ {[
4747+ let json_object fields =
4848+ Jsont.Object (fields, Jsont.Meta.none)
4949+5050+ let json_string s =
5151+ Jsont.String (s, Jsont.Meta.none)
5252+5353+ let json_array items =
5454+ Jsont.Array (items, Jsont.Meta.none)
5555+5656+ let json_field name value =
5757+ ((name, Jsont.Meta.none), value)
5858+5959+ let person_schema =
6060+ json_object [
6161+ json_field "type" (json_string "object");
6262+ json_field "properties" (json_object [
6363+ json_field "name" (json_object [
6464+ json_field "type" (json_string "string")
6565+ ]);
6666+ json_field "age" (json_object [
6767+ json_field "type" (json_string "integer")
6868+ ]);
6969+ ]);
7070+ json_field "required" (json_array [
7171+ json_string "name";
7272+ json_string "age"
7373+ ])
7474+ ]
7575+7676+ let format = Structured_output.of_json_schema person_schema
7777+ ]}
7878+7979+ {2 Usage with Claude Client}
8080+8181+ {[
8282+ let options = Options.default
8383+ |> Options.with_output_format format
8484+8585+ let client = Client.create ~sw ~process_mgr ~options () in
8686+ Client.query client "Extract person info from: John is 30 years old";
8787+8888+ let messages = Client.receive_all client in
8989+ List.iter (function
9090+ | Message.Result result ->
9191+ (match Message.Result.structured_output result with
9292+ | Some json -> (* Process validated JSON *)
9393+ let json_str = match Jsont_bytesrw.encode_string' Jsont.json json with
9494+ | Ok s -> s
9595+ | Error err -> Jsont.Error.to_string err
9696+ in
9797+ Printf.printf "Structured output: %s\n" json_str
9898+ | None -> ())
9999+ | _ -> ()
100100+ ) messages
101101+ ]}
102102+103103+ {2 JSON Schema Support}
104104+105105+ The module supports standard JSON Schema Draft 7, including:
106106+ - Primitive types (string, integer, number, boolean, null)
107107+ - Objects with properties and required fields
108108+ - Arrays with item schemas
109109+ - Enumerations
110110+ - Nested objects and arrays
111111+ - Complex validation rules
112112+113113+ @see <https://json-schema.org/> JSON Schema specification
114114+ @see <https://erratique.ch/software/jsont> jsont documentation *)
115115+116116+(** The log source for structured output operations *)
117117+val src : Logs.Src.t
118118+119119+(** {1 Output Format Configuration} *)
120120+121121+type t
122122+(** The type of structured output format configurations. *)
123123+124124+val of_json_schema : Jsont.json -> t
125125+(** [of_json_schema schema] creates an output format from a JSON Schema.
126126+127127+ The schema should be a valid JSON Schema Draft 7 as a {!Jsont.json} value.
128128+129129+ Example:
130130+ {[
131131+ let meta = Jsont.Meta.none in
132132+ let schema = Jsont.Object ([
133133+ (("type", meta), Jsont.String ("object", meta));
134134+ (("properties", meta), Jsont.Object ([
135135+ (("name", meta), Jsont.Object ([
136136+ (("type", meta), Jsont.String ("string", meta))
137137+ ], meta));
138138+ (("age", meta), Jsont.Object ([
139139+ (("type", meta), Jsont.String ("integer", meta))
140140+ ], meta));
141141+ ], meta));
142142+ (("required", meta), Jsont.Array ([
143143+ Jsont.String ("name", meta);
144144+ Jsont.String ("age", meta)
145145+ ], meta));
146146+ ], meta) in
147147+148148+ let format = Structured_output.of_json_schema schema
149149+ ]} *)
150150+151151+val json_schema : t -> Jsont.json
152152+(** [json_schema t] returns the JSON Schema. *)
153153+154154+val jsont : t Jsont.t
155155+(** Codec for structured output format. *)
156156+157157+(** {1 Serialization}
158158+159159+ Internal use for encoding/decoding with the CLI. *)
160160+161161+val to_json : t -> Jsont.json
162162+(** [to_json t] converts the output format to its JSON representation.
163163+ Internal use only. *)
164164+165165+val of_json : Jsont.json -> t
166166+(** [of_json json] parses an output format from JSON.
167167+ Internal use only.
168168+ @raise Invalid_argument if the JSON is not a valid output format. *)
169169+170170+val pp : Format.formatter -> t -> unit
171171+(** [pp fmt t] pretty-prints the output format. *)
+15-9
claudeio/lib/transport.ml
···8484 let cmd = match Options.output_format options with
8585 | Some format ->
8686 let schema = Structured_output.json_schema format in
8787- let schema_str = Ezjsonm.value_to_string schema in
8787+ let schema_str = match Jsont_bytesrw.encode_string' Jsont.json schema with
8888+ | Ok s -> s
8989+ | Error err -> failwith (Jsont.Error.to_string err)
9090+ in
8891 cmd @ ["--json-schema"; schema_str]
8992 | None -> cmd
9093 in
···162165 { process = P process; stdin; stdin_close; stdout; sw }
163166164167let send t json =
165165- let data = Ezjsonm.value_to_string json in
168168+ let data = match Jsont_bytesrw.encode_string' Jsont.json json with
169169+ | Ok s -> s
170170+ | Error err -> failwith (Jsont.Error.to_string err)
171171+ in
166172 Log.debug (fun m -> m "Sending: %s" data);
167173 try
168174 Eio.Flow.write t.stdin [Cstruct.of_string (data ^ "\n")]
···187193188194let interrupt t =
189195 Log.info (fun m -> m "Sending interrupt signal");
190190- let interrupt_msg =
191191- Ezjsonm.dict [
192192- "type", Ezjsonm.string "control_response";
193193- "response", Ezjsonm.dict [
194194- "subtype", Ezjsonm.string "interrupt";
195195- "request_id", Ezjsonm.string "";
196196- ]
196196+ let interrupt_msg =
197197+ Jsont.Json.object' [
198198+ Jsont.Json.mem (Jsont.Json.name "type") (Jsont.Json.string "control_response");
199199+ Jsont.Json.mem (Jsont.Json.name "response") (Jsont.Json.object' [
200200+ Jsont.Json.mem (Jsont.Json.name "subtype") (Jsont.Json.string "interrupt");
201201+ Jsont.Json.mem (Jsont.Json.name "request_id") (Jsont.Json.string "");
202202+ ])
197203 ]
198204 in
199205 send t interrupt_msg
+2-2
claudeio/lib/transport.mli
···7788type t
991010-val create :
1010+val create :
1111 sw:Eio.Switch.t ->
1212 process_mgr:_ Eio.Process.mgr ->
1313 options:Options.t ->
1414 unit -> t
15151616-val send : t -> Ezjsonm.value -> unit
1616+val send : t -> Jsont.json -> unit
1717val receive_line : t -> string option
1818val interrupt : t -> unit
1919val close : t -> unit
+165
claudeio/test/advanced_config_demo.ml
···11+(* Advanced Configuration Demo
22+33+ This example demonstrates the advanced configuration options available
44+ in the OCaml Claude SDK, including:
55+ - Budget limits for cost control
66+ - Fallback models for reliability
77+ - Settings isolation for CI/CD environments
88+ - Custom buffer sizes for large outputs
99+*)
1010+1111+open Eio.Std
1212+open Claude
1313+1414+let log_setup () =
1515+ Logs.set_reporter (Logs_fmt.reporter ());
1616+ Logs.set_level (Some Logs.Info)
1717+1818+(* Example 1: CI/CD Configuration
1919+2020+ In CI/CD environments, you want isolated, reproducible behavior
2121+ without any user/project/local settings interfering.
2222+*)
2323+let ci_cd_config () =
2424+ Options.default
2525+ |> Options.with_no_settings (* Disable all settings loading *)
2626+ |> Options.with_max_budget_usd 0.50 (* 50 cent limit per run *)
2727+ |> Options.with_fallback_model_string "claude-haiku-4" (* Fast fallback *)
2828+ |> Options.with_model_string "claude-sonnet-4-5"
2929+ |> Options.with_permission_mode Permissions.Mode.Bypass_permissions
3030+3131+(* Example 2: Production Configuration with Fallback
3232+3333+ Production usage with cost controls and automatic fallback
3434+ to ensure availability.
3535+*)
3636+let production_config () =
3737+ Options.default
3838+ |> Options.with_model_string "claude-sonnet-4-5"
3939+ |> Options.with_fallback_model_string "claude-sonnet-3-5"
4040+ |> Options.with_max_budget_usd 10.0 (* $10 limit *)
4141+ |> Options.with_max_buffer_size 5_000_000 (* 5MB buffer for large outputs *)
4242+4343+(* Example 3: Development Configuration
4444+4545+ Development with user settings enabled but with cost controls.
4646+*)
4747+let dev_config () =
4848+ Options.default
4949+ |> Options.with_setting_sources [Options.User; Options.Project]
5050+ |> Options.with_max_budget_usd 1.0 (* $1 limit for dev testing *)
5151+ |> Options.with_fallback_model_string "claude-haiku-4"
5252+5353+(* Example 4: Isolated Test Configuration
5454+5555+ For automated testing with no external settings and strict limits.
5656+*)
5757+let test_config () =
5858+ Options.default
5959+ |> Options.with_no_settings
6060+ |> Options.with_max_budget_usd 0.10 (* 10 cent limit per test *)
6161+ |> Options.with_model_string "claude-haiku-4" (* Fast, cheap model *)
6262+ |> Options.with_permission_mode Permissions.Mode.Bypass_permissions
6363+ |> Options.with_max_buffer_size 1_000_000 (* 1MB buffer *)
6464+6565+(* Example 5: Custom Buffer Size Demo
6666+6767+ For applications that need to handle very large outputs.
6868+*)
6969+let _large_output_config () =
7070+ Options.default
7171+ |> Options.with_max_buffer_size 10_000_000 (* 10MB buffer *)
7272+ |> Options.with_model_string "claude-sonnet-4-5"
7373+7474+(* Helper to run a query with a specific configuration *)
7575+let run_query ~sw process_mgr config prompt =
7676+ print_endline "\n=== Configuration ===";
7777+ (match Options.max_budget_usd config with
7878+ | Some budget -> Printf.printf "Budget limit: $%.2f\n" budget
7979+ | None -> print_endline "Budget limit: None");
8080+ (match Options.fallback_model config with
8181+ | Some model -> Printf.printf "Fallback model: %s\n" (Claude.Model.to_string model)
8282+ | None -> print_endline "Fallback model: None");
8383+ (match Options.setting_sources config with
8484+ | Some [] -> print_endline "Settings: Isolated (no settings loaded)"
8585+ | Some sources ->
8686+ let source_str = String.concat ", " (List.map (function
8787+ | Options.User -> "user"
8888+ | Options.Project -> "project"
8989+ | Options.Local -> "local"
9090+ ) sources) in
9191+ Printf.printf "Settings: %s\n" source_str
9292+ | None -> print_endline "Settings: Default");
9393+ (match Options.max_buffer_size config with
9494+ | Some size -> Printf.printf "Buffer size: %d bytes\n" size
9595+ | None -> print_endline "Buffer size: Default (1MB)");
9696+9797+ print_endline "\n=== Running Query ===";
9898+ let client = Client.create ~options:config ~sw ~process_mgr () in
9999+ Client.query client prompt;
100100+ let messages = Client.receive client in
101101+102102+ Seq.iter (function
103103+ | Message.Assistant msg ->
104104+ List.iter (function
105105+ | Content_block.Text t ->
106106+ Printf.printf "Response: %s\n" (Content_block.Text.text t)
107107+ | _ -> ()
108108+ ) (Message.Assistant.content msg)
109109+ | Message.Result result ->
110110+ Printf.printf "\n=== Session Complete ===\n";
111111+ Printf.printf "Duration: %dms\n" (Message.Result.duration_ms result);
112112+ (match Message.Result.total_cost_usd result with
113113+ | Some cost -> Printf.printf "Cost: $%.4f\n" cost
114114+ | None -> ());
115115+ Printf.printf "Turns: %d\n" (Message.Result.num_turns result)
116116+ | _ -> ()
117117+ ) messages
118118+119119+let main () =
120120+ log_setup ();
121121+122122+ Eio_main.run @@ fun env ->
123123+ Switch.run @@ fun sw ->
124124+ let process_mgr = Eio.Stdenv.process_mgr env in
125125+126126+ print_endline "==============================================";
127127+ print_endline "Claude SDK - Advanced Configuration Examples";
128128+ print_endline "==============================================";
129129+130130+ (* Example: CI/CD isolated environment *)
131131+ print_endline "\n\n### Example 1: CI/CD Configuration ###";
132132+ print_endline "Purpose: Isolated, reproducible environment for CI/CD";
133133+ let config = ci_cd_config () in
134134+ run_query ~sw process_mgr config "What is 2+2? Answer in one sentence.";
135135+136136+ (* Example: Production with fallback *)
137137+ print_endline "\n\n### Example 2: Production Configuration ###";
138138+ print_endline "Purpose: Production with cost controls and fallback";
139139+ let config = production_config () in
140140+ run_query ~sw process_mgr config "Explain OCaml in one sentence.";
141141+142142+ (* Example: Development with settings *)
143143+ print_endline "\n\n### Example 3: Development Configuration ###";
144144+ print_endline "Purpose: Development with user/project settings";
145145+ let config = dev_config () in
146146+ run_query ~sw process_mgr config "What is functional programming? One sentence.";
147147+148148+ (* Example: Test configuration *)
149149+ print_endline "\n\n### Example 4: Test Configuration ###";
150150+ print_endline "Purpose: Automated testing with strict limits";
151151+ let config = test_config () in
152152+ run_query ~sw process_mgr config "Say 'test passed' in one word.";
153153+154154+ print_endline "\n\n==============================================";
155155+ print_endline "All examples completed successfully!";
156156+ print_endline "=============================================="
157157+158158+let () =
159159+ try
160160+ main ()
161161+ with
162162+ | e ->
163163+ Printf.eprintf "Error: %s\n" (Printexc.to_string e);
164164+ Printexc.print_backtrace stderr;
165165+ exit 1
···11+open Claude
22+open Eio.Std
33+44+let () = Logs.set_reporter (Logs_fmt.reporter ())
55+let () = Logs.set_level (Some Logs.Info)
66+77+let run env =
88+ Switch.run @@ fun sw ->
99+ let process_mgr = Eio.Stdenv.process_mgr env in
1010+1111+ (* Create client with default options *)
1212+ let options = Options.default in
1313+ let client = Client.create ~options ~sw ~process_mgr () in
1414+1515+ traceln "=== Dynamic Control Demo ===\n";
1616+1717+ (* First query with default model *)
1818+ traceln "1. Initial query with default model";
1919+ Client.query client "What model are you?";
2020+2121+ (* Consume initial messages *)
2222+ let messages = Client.receive_all client in
2323+ List.iter (function
2424+ | Message.Assistant msg ->
2525+ List.iter (function
2626+ | Content_block.Text t ->
2727+ traceln "Assistant: %s" (Content_block.Text.text t)
2828+ | _ -> ()
2929+ ) (Message.Assistant.content msg)
3030+ | _ -> ()
3131+ ) messages;
3232+3333+ traceln "\n2. Getting server info...";
3434+ (try
3535+ let info = Client.get_server_info client in
3636+ traceln "Server version: %s" (Sdk_control.Server_info.version info);
3737+ traceln "Capabilities: [%s]"
3838+ (String.concat ", " (Sdk_control.Server_info.capabilities info));
3939+ traceln "Commands: [%s]"
4040+ (String.concat ", " (Sdk_control.Server_info.commands info));
4141+ traceln "Output styles: [%s]"
4242+ (String.concat ", " (Sdk_control.Server_info.output_styles info));
4343+ with
4444+ | Failure msg -> traceln "Failed to get server info: %s" msg
4545+ | exn -> traceln "Error getting server info: %s" (Printexc.to_string exn));
4646+4747+ traceln "\n3. Switching to a different model (if available)...";
4848+ (try
4949+ Client.set_model_string client "claude-sonnet-4";
5050+ traceln "Model switched successfully";
5151+5252+ (* Query with new model *)
5353+ Client.query client "Confirm your model again please.";
5454+ let messages = Client.receive_all client in
5555+ List.iter (function
5656+ | Message.Assistant msg ->
5757+ List.iter (function
5858+ | Content_block.Text t ->
5959+ traceln "Assistant (new model): %s" (Content_block.Text.text t)
6060+ | _ -> ()
6161+ ) (Message.Assistant.content msg)
6262+ | _ -> ()
6363+ ) messages;
6464+ with
6565+ | Failure msg -> traceln "Failed to switch model: %s" msg
6666+ | exn -> traceln "Error switching model: %s" (Printexc.to_string exn));
6767+6868+ traceln "\n4. Changing permission mode...";
6969+ (try
7070+ Client.set_permission_mode client Permissions.Mode.Accept_edits;
7171+ traceln "Permission mode changed to Accept_edits";
7272+ with
7373+ | Failure msg -> traceln "Failed to change permission mode: %s" msg
7474+ | exn -> traceln "Error changing permission mode: %s" (Printexc.to_string exn));
7575+7676+ traceln "\n=== Demo Complete ===";
7777+ ()
7878+7979+let () =
8080+ Eio_main.run @@ fun env ->
8181+ try
8282+ run env
8383+ with
8484+ | Transport.CLI_not_found msg ->
8585+ traceln "Error: %s" msg;
8686+ traceln "Make sure the 'claude' CLI is installed and authenticated.";
8787+ exit 1
8888+ | exn ->
8989+ traceln "Unexpected error: %s" (Printexc.to_string exn);
9090+ Printexc.print_backtrace stderr;
9191+ exit 1
+2-2
claudeio/test/hooks_example.ml
···10101111 if tool_name = "Bash" then
1212 let tool_input = Claude.Hooks.PreToolUse.tool_input hook in
1313- match Ezjsonm.find tool_input ["command"] with
1414- | `String command ->
1313+ match Test_json_utils.get_string tool_input "command" with
1414+ | Some command ->
1515 if String.length command >= 6 && String.sub command 0 6 = "rm -rf" then begin
1616 Log.app (fun m -> m "🚫 Blocked dangerous command: %s" command);
1717 let output = Claude.Hooks.PreToolUse.deny
+27-22
claudeio/test/permission_demo.ml
···3333 Log.app (fun m -> m "Tool: %s" tool_name);
34343535 (* Log the full input for debugging *)
3636- Log.info (fun m -> m "Full input JSON: %s" (Ezjsonm.value_to_string input));
3636+ Log.info (fun m -> m "Full input JSON: %s" (Test_json_utils.to_string input));
37373838 (* Show input details *)
3939 (* Try to extract key information from the input *)
4040 (try
4141 match tool_name with
4242 | "Read" ->
4343- let file_path = Ezjsonm.find input ["file_path"] |> Ezjsonm.get_string in
4444- Log.app (fun m -> m "File: %s" file_path)
4343+ (match Test_json_utils.get_string input "file_path" with
4444+ | Some file_path -> Log.app (fun m -> m "File: %s" file_path)
4545+ | None -> ())
4546 | "Bash" ->
4646- let command = Ezjsonm.find input ["command"] |> Ezjsonm.get_string in
4747- Log.app (fun m -> m "Command: %s" command)
4747+ (match Test_json_utils.get_string input "command" with
4848+ | Some command -> Log.app (fun m -> m "Command: %s" command)
4949+ | None -> ())
4850 | "Write" | "Edit" ->
4949- let file_path = Ezjsonm.find input ["file_path"] |> Ezjsonm.get_string in
5050- Log.app (fun m -> m "File: %s" file_path)
5151+ (match Test_json_utils.get_string input "file_path" with
5252+ | Some file_path -> Log.app (fun m -> m "File: %s" file_path)
5353+ | None -> ())
5154 | "Glob" ->
5252- let pattern = Ezjsonm.find input ["pattern"] |> Ezjsonm.get_string in
5353- Log.app (fun m -> m "Pattern: %s" pattern);
5454- (try
5555- let path = Ezjsonm.find input ["path"] |> Ezjsonm.get_string in
5656- Log.app (fun m -> m "Path: %s" path)
5757- with _ -> Log.app (fun m -> m "Path: (current directory)"))
5555+ (match Test_json_utils.get_string input "pattern" with
5656+ | Some pattern ->
5757+ Log.app (fun m -> m "Pattern: %s" pattern);
5858+ (match Test_json_utils.get_string input "path" with
5959+ | Some path -> Log.app (fun m -> m "Path: %s" path)
6060+ | None -> Log.app (fun m -> m "Path: (current directory)"))
6161+ | None -> ())
5862 | "Grep" ->
5959- let pattern = Ezjsonm.find input ["pattern"] |> Ezjsonm.get_string in
6060- Log.app (fun m -> m "Pattern: %s" pattern);
6161- (try
6262- let path = Ezjsonm.find input ["path"] |> Ezjsonm.get_string in
6363- Log.app (fun m -> m "Path: %s" path)
6464- with _ -> Log.app (fun m -> m "Path: (current directory)"))
6363+ (match Test_json_utils.get_string input "pattern" with
6464+ | Some pattern ->
6565+ Log.app (fun m -> m "Pattern: %s" pattern);
6666+ (match Test_json_utils.get_string input "path" with
6767+ | Some path -> Log.app (fun m -> m "Path: %s" path)
6868+ | None -> Log.app (fun m -> m "Path: (current directory)"))
6969+ | None -> ())
6570 | _ ->
6666- Log.app (fun m -> m "Input: %s" (Ezjsonm.value_to_string input))
6767- with exn ->
7171+ Log.app (fun m -> m "Input: %s" (Test_json_utils.to_string input))
7272+ with exn ->
6873 Log.info (fun m -> m "Failed to parse input details: %s" (Printexc.to_string exn)));
69747075 (* Check if already granted *)
···9095 | _ ->
9196 Granted.deny tool_name;
9297 Log.info (fun m -> m "User denied permission for %s" tool_name);
9393- Claude.Permissions.Result.deny ~message:(Printf.sprintf "User denied access to %s" tool_name) ~interrupt:false
9898+ Claude.Permissions.Result.deny ~message:(Printf.sprintf "User denied access to %s" tool_name) ~interrupt:false ()
9499 end
9510096101let process_response client =
+1-1
claudeio/test/simple_permission_test.ml
···77let auto_allow_callback ~tool_name ~input ~context:_ =
88 Log.app (fun m -> m "\n🔐 Permission callback invoked!");
99 Log.app (fun m -> m " Tool: %s" tool_name);
1010- Log.app (fun m -> m " Input: %s" (Ezjsonm.value_to_string input));
1010+ Log.app (fun m -> m " Input: %s" (Test_json_utils.to_string input));
1111 Log.app (fun m -> m " ✅ Auto-allowing");
1212 Claude.Permissions.Result.allow ()
1313
+29-17
claudeio/test/simulated_permissions.ml
···4646 Claude.Permissions.Result.allow ()
4747 end else if PermissionState.is_denied tool_name then begin
4848 Log.app (fun m -> m " → Auto-denied (previously denied)");
4949- Claude.Permissions.Result.deny
4949+ Claude.Permissions.Result.deny
5050 ~message:(Printf.sprintf "Tool %s is blocked by policy" tool_name)
5151- ~interrupt:false
5151+ ~interrupt:false ()
5252 end else begin
5353 (* Ask user *)
5454 Printf.printf " Allow %s? [y/n/always/never]: %!" tool_name;
···5858 Claude.Permissions.Result.allow ()
5959 | "n" | "no" ->
6060 Log.app (fun m -> m " → Denied (one time)");
6161- Claude.Permissions.Result.deny
6161+ Claude.Permissions.Result.deny
6262 ~message:(Printf.sprintf "User denied %s" tool_name)
6363- ~interrupt:false
6363+ ~interrupt:false ()
6464 | "a" | "always" ->
6565 PermissionState.grant tool_name;
6666 Log.app (fun m -> m " → Allowed (always)");
···6868 | "never" ->
6969 PermissionState.deny tool_name;
7070 Log.app (fun m -> m " → Denied (always)");
7171- Claude.Permissions.Result.deny
7171+ Claude.Permissions.Result.deny
7272 ~message:(Printf.sprintf "Tool %s permanently blocked" tool_name)
7373- ~interrupt:false
7373+ ~interrupt:false ()
7474 | _ ->
7575 Log.app (fun m -> m " → Denied (invalid response)");
7676- Claude.Permissions.Result.deny
7676+ Claude.Permissions.Result.deny
7777 ~message:"Invalid permission response"
7878- ~interrupt:false
7878+ ~interrupt:false ()
7979 end
80808181(* Demonstrate the permission system *)
···92929393 (* Test each tool *)
9494 List.iter (fun tool ->
9595- let input = Ezjsonm.dict [
9696- "file_path", Ezjsonm.string "/example/path.txt"
9797- ] in
9898- let result = example_permission_callback
9595+ let input =
9696+ let open Jsont in
9797+ Object ([
9898+ (("file_path", Meta.none), String ("/example/path.txt", Meta.none))
9999+ ], Meta.none)
100100+ in
101101+ let result = example_permission_callback
99102 ~tool_name:tool ~input ~context in
100103101104 (* Show result *)
···118121 let callback = Claude.Permissions.discovery_callback discovered in
119122120123 (* Simulate some tool requests *)
121121- let requests = [
122122- ("Read", Ezjsonm.dict ["file_path", Ezjsonm.string "test.ml"]);
123123- ("Bash", Ezjsonm.dict ["command", Ezjsonm.string "ls -la"]);
124124- ("Write", Ezjsonm.dict ["file_path", Ezjsonm.string "output.txt"]);
125125- ] in
124124+ let requests =
125125+ let open Jsont in
126126+ [
127127+ ("Read", Object ([
128128+ (("file_path", Meta.none), String ("test.ml", Meta.none))
129129+ ], Meta.none));
130130+ ("Bash", Object ([
131131+ (("command", Meta.none), String ("ls -la", Meta.none))
132132+ ], Meta.none));
133133+ ("Write", Object ([
134134+ (("file_path", Meta.none), String ("output.txt", Meta.none))
135135+ ], Meta.none));
136136+ ]
137137+ in
126138127139 Log.app (fun m -> m "Simulating tool requests with discovery callback...\n");
128140
+172
claudeio/test/structured_output_demo.ml
···11+(* Example demonstrating structured output with JSON Schema *)
22+33+module C = Claude
44+55+let () =
66+ (* Configure logging to see what's happening *)
77+ Logs.set_reporter (Logs_fmt.reporter ());
88+ Logs.set_level (Some Logs.Info);
99+ Logs.Src.set_level C.Message.src (Some Logs.Debug)
1010+1111+let run_codebase_analysis env =
1212+ Printf.printf "\n=== Codebase Analysis with Structured Output ===\n\n";
1313+1414+ (* Define the JSON Schema for our expected output structure *)
1515+ let analysis_schema =
1616+ let open Jsont in
1717+ Object ([
1818+ (("type", Meta.none), String ("object", Meta.none));
1919+ (("properties", Meta.none), Object ([
2020+ (("file_count", Meta.none), Object ([
2121+ (("type", Meta.none), String ("integer", Meta.none));
2222+ (("description", Meta.none), String ("Total number of files analyzed", Meta.none))
2323+ ], Meta.none));
2424+ (("has_tests", Meta.none), Object ([
2525+ (("type", Meta.none), String ("boolean", Meta.none));
2626+ (("description", Meta.none), String ("Whether the codebase has test files", Meta.none))
2727+ ], Meta.none));
2828+ (("primary_language", Meta.none), Object ([
2929+ (("type", Meta.none), String ("string", Meta.none));
3030+ (("description", Meta.none), String ("The primary programming language used", Meta.none))
3131+ ], Meta.none));
3232+ (("complexity_rating", Meta.none), Object ([
3333+ (("type", Meta.none), String ("string", Meta.none));
3434+ (("enum", Meta.none), Array ([
3535+ String ("low", Meta.none);
3636+ String ("medium", Meta.none);
3737+ String ("high", Meta.none)
3838+ ], Meta.none));
3939+ (("description", Meta.none), String ("Overall complexity rating", Meta.none))
4040+ ], Meta.none));
4141+ (("key_findings", Meta.none), Object ([
4242+ (("type", Meta.none), String ("array", Meta.none));
4343+ (("items", Meta.none), Object ([
4444+ (("type", Meta.none), String ("string", Meta.none))
4545+ ], Meta.none));
4646+ (("description", Meta.none), String ("List of key findings from the analysis", Meta.none))
4747+ ], Meta.none));
4848+ ], Meta.none));
4949+ (("required", Meta.none), Array ([
5050+ String ("file_count", Meta.none);
5151+ String ("has_tests", Meta.none);
5252+ String ("primary_language", Meta.none);
5353+ String ("complexity_rating", Meta.none);
5454+ String ("key_findings", Meta.none)
5555+ ], Meta.none));
5656+ (("additionalProperties", Meta.none), Bool (false, Meta.none))
5757+ ], Meta.none)
5858+ in
5959+6060+ (* Create structured output format from the schema *)
6161+ let output_format = C.Structured_output.of_json_schema analysis_schema in
6262+6363+ (* Configure Claude with structured output *)
6464+ let options = C.Options.default
6565+ |> C.Options.with_output_format output_format
6666+ |> C.Options.with_allowed_tools ["Read"; "Glob"; "Grep"]
6767+ |> C.Options.with_system_prompt
6868+ "You are a code analysis assistant. Analyze codebases and provide \
6969+ structured output matching the given JSON Schema."
7070+ in
7171+7272+ Printf.printf "Structured output format configured\n";
7373+ Printf.printf "Schema: %s\n\n"
7474+ (Test_json_utils.to_string ~minify:false analysis_schema);
7575+7676+ (* Create Claude client and query *)
7777+ Eio.Switch.run @@ fun sw ->
7878+ let process_mgr = Eio.Stdenv.process_mgr env in
7979+ let client = C.Client.create ~sw ~process_mgr ~options () in
8080+8181+ let prompt =
8282+ "Please analyze the current codebase structure. Look at the files, \
8383+ identify the primary language, count files, check for tests, assess \
8484+ complexity, and provide key findings. Return your analysis in the \
8585+ structured JSON format I specified."
8686+ in
8787+8888+ Printf.printf "Sending query: %s\n\n" prompt;
8989+ C.Client.query client prompt;
9090+9191+ (* Process responses *)
9292+ let messages = C.Client.receive client in
9393+ Seq.iter (function
9494+ | C.Message.Assistant msg ->
9595+ Printf.printf "\nAssistant response:\n";
9696+ List.iter (function
9797+ | C.Content_block.Text text ->
9898+ Printf.printf " Text: %s\n" (C.Content_block.Text.text text)
9999+ | C.Content_block.Tool_use tool ->
100100+ Printf.printf " Using tool: %s\n" (C.Content_block.Tool_use.name tool)
101101+ | _ -> ()
102102+ ) (C.Message.Assistant.content msg)
103103+104104+ | C.Message.Result result ->
105105+ Printf.printf "\n=== Result ===\n";
106106+ Printf.printf "Duration: %dms\n" (C.Message.Result.duration_ms result);
107107+ Printf.printf "Cost: $%.4f\n"
108108+ (Option.value (C.Message.Result.total_cost_usd result) ~default:0.0);
109109+110110+ (* Extract and display structured output *)
111111+ (match C.Message.Result.structured_output result with
112112+ | Some output ->
113113+ Printf.printf "\n=== Structured Output ===\n";
114114+ Printf.printf "%s\n\n" (Test_json_utils.to_string ~minify:false output);
115115+116116+ (* Parse the structured output *)
117117+ let file_count = Test_json_utils.get_int output "file_count" |> Option.value ~default:0 in
118118+ let has_tests = Test_json_utils.get_bool output "has_tests" |> Option.value ~default:false in
119119+ let language = Test_json_utils.get_string output "primary_language" |> Option.value ~default:"unknown" in
120120+ let complexity = Test_json_utils.get_string output "complexity_rating" |> Option.value ~default:"unknown" in
121121+ let findings =
122122+ match Test_json_utils.get_array output "key_findings" with
123123+ | Some items ->
124124+ List.filter_map (fun json ->
125125+ Test_json_utils.as_string json
126126+ ) items
127127+ | None -> []
128128+ in
129129+130130+ Printf.printf "=== Parsed Analysis ===\n";
131131+ Printf.printf "File Count: %d\n" file_count;
132132+ Printf.printf "Has Tests: %b\n" has_tests;
133133+ Printf.printf "Primary Language: %s\n" language;
134134+ Printf.printf "Complexity: %s\n" complexity;
135135+ Printf.printf "Key Findings:\n";
136136+ List.iter (fun finding ->
137137+ Printf.printf " - %s\n" finding
138138+ ) findings
139139+140140+ | None ->
141141+ Printf.printf "No structured output received\n";
142142+ (match C.Message.Result.result result with
143143+ | Some text -> Printf.printf "Text result: %s\n" text
144144+ | None -> ()))
145145+146146+ | C.Message.System sys ->
147147+ (match C.Message.System.subtype sys with
148148+ | "init" ->
149149+ Printf.printf "Session initialized\n"
150150+ | _ -> ())
151151+152152+ | _ -> ()
153153+ ) messages;
154154+155155+ Printf.printf "\nDone!\n"
156156+157157+let () =
158158+ Eio_main.run @@ fun env ->
159159+ try
160160+ run_codebase_analysis env
161161+ with
162162+ | C.Transport.CLI_not_found msg ->
163163+ Printf.eprintf "Error: Claude CLI not found\n%s\n" msg;
164164+ Printf.eprintf "Make sure 'claude' is installed and in your PATH\n";
165165+ exit 1
166166+ | C.Transport.Connection_error msg ->
167167+ Printf.eprintf "Connection error: %s\n" msg;
168168+ exit 1
169169+ | exn ->
170170+ Printf.eprintf "Unexpected error: %s\n" (Printexc.to_string exn);
171171+ Printexc.print_backtrace stderr;
172172+ exit 1
+72
claudeio/test/structured_output_simple.ml
···11+(* Simple example showing structured output with explicit JSON Schema *)
22+33+module C = Claude
44+55+let () =
66+ Logs.set_reporter (Logs_fmt.reporter ());
77+ Logs.set_level (Some Logs.Info)
88+99+let simple_example env =
1010+ Printf.printf "\n=== Simple Structured Output Example ===\n\n";
1111+1212+ (* Define a simple schema for a person's info *)
1313+ let person_schema =
1414+ let open Jsont in
1515+ Object ([
1616+ (("type", Meta.none), String ("object", Meta.none));
1717+ (("properties", Meta.none), Object ([
1818+ (("name", Meta.none), Object ([
1919+ (("type", Meta.none), String ("string", Meta.none))
2020+ ], Meta.none));
2121+ (("age", Meta.none), Object ([
2222+ (("type", Meta.none), String ("integer", Meta.none))
2323+ ], Meta.none));
2424+ (("occupation", Meta.none), Object ([
2525+ (("type", Meta.none), String ("string", Meta.none))
2626+ ], Meta.none));
2727+ ], Meta.none));
2828+ (("required", Meta.none), Array ([
2929+ String ("name", Meta.none);
3030+ String ("age", Meta.none);
3131+ String ("occupation", Meta.none)
3232+ ], Meta.none))
3333+ ], Meta.none)
3434+ in
3535+3636+ let output_format = C.Structured_output.of_json_schema person_schema in
3737+3838+ let options = C.Options.default
3939+ |> C.Options.with_output_format output_format
4040+ |> C.Options.with_max_turns 1
4141+ in
4242+4343+ Printf.printf "Asking Claude to provide structured data...\n\n";
4444+4545+ Eio.Switch.run @@ fun sw ->
4646+ let process_mgr = Eio.Stdenv.process_mgr env in
4747+ let client = C.Client.create ~sw ~process_mgr ~options () in
4848+4949+ C.Client.query client
5050+ "Tell me about a famous computer scientist. Provide their name, age, \
5151+ and occupation in the exact JSON structure I specified.";
5252+5353+ let messages = C.Client.receive_all client in
5454+ List.iter (function
5555+ | C.Message.Result result ->
5656+ Printf.printf "Response received!\n";
5757+ (match C.Message.Result.structured_output result with
5858+ | Some json ->
5959+ Printf.printf "\nStructured Output:\n%s\n"
6060+ (Test_json_utils.to_string ~minify:false json)
6161+ | None ->
6262+ Printf.printf "No structured output\n")
6363+ | _ -> ()
6464+ ) messages
6565+6666+let () =
6767+ Eio_main.run @@ fun env ->
6868+ try
6969+ simple_example env
7070+ with exn ->
7171+ Printf.eprintf "Error: %s\n" (Printexc.to_string exn);
7272+ exit 1
+78
claudeio/test/test_incoming.ml
···11+(** Test the Incoming message codec *)
22+33+open Claude
44+55+let test_decode_user_message () =
66+ 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 _)) ->
99+ print_endline "✓ Decoded user message successfully"
1010+ | Ok _ ->
1111+ print_endline "✗ Wrong message type decoded"
1212+ | Error err ->
1313+ Printf.printf "✗ Failed to decode user message: %s\n" (Jsont.Error.to_string err)
1414+1515+let test_decode_assistant_message () =
1616+ let json_str = {|{"type":"assistant","model":"claude-sonnet-4","content":[{"type":"text","text":"Hi"}]}|} in
1717+ match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
1818+ | Ok (Incoming.Message (Message.Assistant _)) ->
1919+ print_endline "✓ Decoded assistant message successfully"
2020+ | Ok _ ->
2121+ print_endline "✗ Wrong message type decoded"
2222+ | Error err ->
2323+ Printf.printf "✗ Failed to decode assistant message: %s\n" (Jsont.Error.to_string err)
2424+2525+let test_decode_system_message () =
2626+ let json_str = {|{"type":"system","subtype":"init","data":{"session_id":"test-123"}}|} in
2727+ match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
2828+ | Ok (Incoming.Message (Message.System _)) ->
2929+ print_endline "✓ Decoded system message successfully"
3030+ | Ok _ ->
3131+ print_endline "✗ Wrong message type decoded"
3232+ | Error err ->
3333+ Printf.printf "✗ Failed to decode system message: %s\n" (Jsont.Error.to_string err)
3434+3535+let test_decode_control_response () =
3636+ let json_str = {|{"type":"control_response","response":{"subtype":"success","request_id":"test-req-1"}}|} in
3737+ match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
3838+ | Ok (Incoming.Control_response resp) ->
3939+ (match resp.response with
4040+ | Sdk_control.Response.Success s ->
4141+ if s.request_id = "test-req-1" then
4242+ print_endline "✓ Decoded control response successfully"
4343+ else
4444+ Printf.printf "✗ Wrong request_id: %s\n" s.request_id
4545+ | Sdk_control.Response.Error _ ->
4646+ print_endline "✗ Got error response instead of success")
4747+ | Ok _ ->
4848+ print_endline "✗ Wrong message type decoded"
4949+ | Error err ->
5050+ Printf.printf "✗ Failed to decode control response: %s\n" (Jsont.Error.to_string err)
5151+5252+let test_decode_control_response_error () =
5353+ let json_str = {|{"type":"control_response","response":{"subtype":"error","request_id":"test-req-2","error":"Something went wrong"}}|} in
5454+ match Jsont_bytesrw.decode_string' Incoming.jsont json_str with
5555+ | Ok (Incoming.Control_response resp) ->
5656+ (match resp.response with
5757+ | Sdk_control.Response.Error e ->
5858+ if e.request_id = "test-req-2" && e.error = "Something went wrong" then
5959+ print_endline "✓ Decoded control error response successfully"
6060+ else
6161+ Printf.printf "✗ Wrong error content\n"
6262+ | Sdk_control.Response.Success _ ->
6363+ print_endline "✗ Got success response instead of error")
6464+ | Ok _ ->
6565+ print_endline "✗ Wrong message type decoded"
6666+ | Error err ->
6767+ Printf.printf "✗ Failed to decode control error response: %s\n" (Jsont.Error.to_string err)
6868+6969+let () =
7070+ print_endline "Testing Incoming message codec...";
7171+ print_endline "";
7272+ test_decode_user_message ();
7373+ test_decode_assistant_message ();
7474+ test_decode_system_message ();
7575+ test_decode_control_response ();
7676+ test_decode_control_response_error ();
7777+ print_endline "";
7878+ print_endline "All tests completed!"
+41
claudeio/test/test_json_utils.ml
···11+(* Helper functions for JSON operations in tests *)
22+33+let to_string ?(minify=false) json =
44+ let format = if minify then Jsont.Minify else Jsont.Indent in
55+ match Jsont_bytesrw.encode_string' ~format Jsont.json json with
66+ | Ok s -> s
77+ | Error err -> Jsont.Error.to_string err
88+99+let get_field json key =
1010+ match json with
1111+ | Jsont.Object (members, _) ->
1212+ List.find_map (fun ((name, _), value) ->
1313+ if name = key then Some value else None
1414+ ) members
1515+ | _ -> None
1616+1717+let get_string json key =
1818+ match get_field json key with
1919+ | Some (Jsont.String (s, _)) -> Some s
2020+ | _ -> None
2121+2222+let get_int json key =
2323+ match get_field json key with
2424+ | Some (Jsont.Number (f, _)) ->
2525+ let i = int_of_float f in
2626+ if float_of_int i = f then Some i else None
2727+ | _ -> None
2828+2929+let get_bool json key =
3030+ match get_field json key with
3131+ | Some (Jsont.Bool (b, _)) -> Some b
3232+ | _ -> None
3333+3434+let get_array json key =
3535+ match get_field json key with
3636+ | Some (Jsont.Array (items, _)) -> Some items
3737+ | _ -> None
3838+3939+let as_string = function
4040+ | Jsont.String (s, _) -> Some s
4141+ | _ -> None