···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 *)
44+(** Control response builders using jsont *)
55+module Control_response = struct
66+ let success ~request_id ~response =
77+ Jsont.Json.object' [
88+ Jsont.Json.mem (Jsont.Json.name "type") (Jsont.Json.string "control_response");
99+ Jsont.Json.mem (Jsont.Json.name "response") (Jsont.Json.object' [
1010+ Jsont.Json.mem (Jsont.Json.name "subtype") (Jsont.Json.string "success");
1111+ Jsont.Json.mem (Jsont.Json.name "request_id") (Jsont.Json.string request_id);
1212+ Jsont.Json.mem (Jsont.Json.name "response") response;
1313+ ]);
1414+ ]
1515+1616+ let error ~request_id ~message =
1717+ Jsont.Json.object' [
1818+ Jsont.Json.mem (Jsont.Json.name "type") (Jsont.Json.string "control_response");
1919+ Jsont.Json.mem (Jsont.Json.name "response") (Jsont.Json.object' [
2020+ Jsont.Json.mem (Jsont.Json.name "subtype") (Jsont.Json.string "error");
2121+ Jsont.Json.mem (Jsont.Json.name "request_id") (Jsont.Json.string request_id);
2222+ Jsont.Json.mem (Jsont.Json.name "error") (Jsont.Json.string message);
2323+ ]);
2424+ ]
2525+end
2626+2727+(* Helper functions for JSON manipulation using jsont *)
528let json_to_string json =
629 match Jsont_bytesrw.encode_string' Jsont.json json with
730 | Ok s -> s
831 | Error err -> failwith (Jsont.Error.to_string err)
9321010-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
3333+(* JSON construction helpers using jsont *)
3434+let json_string s = Jsont.Json.string s
3535+let json_null () = Jsont.Json.null ()
22362323-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)
3737+let json_object pairs =
3838+ Jsont.Json.object' (List.map (fun (k, v) -> Jsont.Json.mem (Jsont.Json.name k) v) pairs)
43394440type t = {
4541 transport : Transport.t;
···5349 control_condition : Eio.Condition.t;
5450}
55515656-let handle_control_request t control_msg =
5757- let data = Control.data control_msg in
5858- Log.info (fun m -> m "Handling control request: %s" (Control.subtype control_msg));
5959- Log.info (fun m -> m "Control request data: %s" (json_to_string data));
6060- match find_string data ["request"; "subtype"] with
6161- | "can_use_tool" ->
6262- let tool_name = find_string data ["request"; "tool_name"] in
6363- let input = find data ["request"; "input"] in
5252+let handle_control_request t (ctrl_req : Incoming.Control_request.t) =
5353+ let request_id = Incoming.Control_request.request_id ctrl_req in
5454+ Log.info (fun m -> m "Handling control request: %s" (Incoming.Control_request.subtype ctrl_req));
5555+5656+ match Incoming.Control_request.request ctrl_req with
5757+ | Incoming.Control_request.Can_use_tool req ->
5858+ let tool_name = Incoming.Control_request.Can_use_tool.tool_name req in
5959+ let input = Incoming.Control_request.Can_use_tool.input req in
6460 Log.info (fun m -> m "Permission request for tool '%s' with input: %s"
6561 tool_name (json_to_string input));
6666- let suggestions =
6767- try
6868- let sugg_json = find data ["request"; "permission_suggestions"] in
6969- match sugg_json with
7070- | Jsont.Array _ ->
7171- (* TODO: Parse permission suggestions *)
7272- []
7373- | _ -> []
7474- with Not_found -> []
7575- in
7676- let context = Permissions.Context.create ~suggestions () in
6262+ (* TODO: Parse permission_suggestions properly *)
6363+ let context = Permissions.Context.create ~suggestions:[] () in
77647865 Log.info (fun m -> m "Invoking permission callback for tool: %s" tool_name);
7966 let result = match t.permission_callback with
···8976 | Permissions.Result.Allow _ -> "ALLOW"
9077 | Permissions.Result.Deny _ -> "DENY"));
91789292- (* Convert permission result to CLI format: {"behavior": "allow", "updatedInput": ...} or {"behavior": "deny", "message": ...} *)
7979+ (* Convert permission result to CLI format *)
9380 let response_data = match result with
9481 | Permissions.Result.Allow { updated_input; updated_permissions = _; unknown = _ } ->
9595- (* updatedInput is required when allowing - use original input if not modified *)
9696- let updated_input = match updated_input with
9797- | Some inp -> inp
9898- | None -> input (* Use original input *)
9999- in
100100- json_dict [
8282+ let updated_input = Option.value updated_input ~default:input in
8383+ json_object [
10184 ("behavior", json_string "allow");
10285 ("updatedInput", updated_input);
10386 ]
10487 | Permissions.Result.Deny { message; interrupt = _; unknown = _ } ->
105105- json_dict [
8888+ json_object [
10689 ("behavior", json_string "deny");
10790 ("message", json_string message);
10891 ]
10992 in
110110-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);
116116- "response", response_data
117117- ]
118118- ] in
9393+ let response = Control_response.success ~request_id ~response:response_data in
11994 Log.info (fun m -> m "Sending control response: %s" (json_to_string response));
12095 Transport.send t.transport response
121121-122122- | "hook_callback" ->
123123- let callback_id = find_string data ["request"; "callback_id"] in
124124- let input = find data ["request"; "input"] in
125125- let tool_use_id =
126126- try Some (find_string data ["request"; "tool_use_id"])
127127- with Not_found -> None
128128- in
9696+9797+ | Incoming.Control_request.Hook_callback req ->
9898+ let callback_id = Incoming.Control_request.Hook_callback.callback_id req in
9999+ let input = Incoming.Control_request.Hook_callback.input req in
100100+ let tool_use_id = Incoming.Control_request.Hook_callback.tool_use_id req in
129101 Log.info (fun m -> m "Hook callback request for callback_id: %s" callback_id);
130102131103 (try
···137109 | Ok j -> j
138110 | Error msg -> failwith ("Failed to encode hook result: " ^ msg)
139111 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
147147- ]
148148- ] in
112112+ let response = Control_response.success ~request_id ~response:result_json in
149113 Log.info (fun m -> m "Hook callback succeeded, sending response");
150114 Transport.send t.transport response
151115 with
152116 | Not_found ->
153117 let error_msg = Printf.sprintf "Hook callback not found: %s" callback_id in
154118 Log.err (fun m -> m "%s" 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
161161- ]
162162- ] in
163163- Transport.send t.transport response
119119+ Transport.send t.transport (Control_response.error ~request_id ~message:error_msg)
164120 | exn ->
165121 let error_msg = Printf.sprintf "Hook callback error: %s" (Printexc.to_string exn) in
166122 Log.err (fun m -> m "%s" 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
173173- ]
174174- ] in
175175- Transport.send t.transport response)
123123+ Transport.send t.transport (Control_response.error ~request_id ~message:error_msg))
176124177177- | subtype ->
178178- (* Respond with error for unknown control requests *)
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)
185185- ]
186186- ] in
187187- Transport.send t.transport response
125125+ | Incoming.Control_request.Unknown (subtype, _) ->
126126+ let error_msg = Printf.sprintf "Unsupported control request: %s" subtype in
127127+ Transport.send t.transport (Control_response.error ~request_id ~message:error_msg)
188128189129let handle_control_response t control_resp =
190130 let request_id = match control_resp.Sdk_control.response with
···211151 Log.debug (fun m -> m "Handle messages: EOF received");
212152 Seq.Nil
213153 | Some line ->
214214- try
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
218218- | "control_request" ->
219219- let control_msg = Control.create
220220- ~request_id:(find_string json ["request_id"])
221221- ~subtype:(find_string json ["request"; "subtype"])
222222- ~data:json in
223223- Log.info (fun m -> m "Received control request: %s (request_id: %s)"
224224- (Control.subtype control_msg) (Control.request_id control_msg));
225225- handle_control_request t control_msg;
226226- loop ()
154154+ (* Use unified Incoming codec for all message types *)
155155+ match Jsont_bytesrw.decode_string' Incoming.jsont line with
156156+ | Ok (Incoming.Message msg) ->
157157+ Log.info (fun m -> m "← %a" Message.pp msg);
227158228228- | _ ->
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);
159159+ (* Extract session ID from system messages *)
160160+ (match msg with
161161+ | Message.System sys ->
162162+ (match Message.System.Data.session_id (Message.System.data sys) with
163163+ | Some session_id ->
164164+ t.session_id <- Some session_id;
165165+ Log.debug (fun m -> m "Stored session ID: %s" session_id)
166166+ | None -> ())
167167+ | _ -> ());
233168234234- (* 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- | _ -> ());
169169+ Seq.Cons (msg, loop)
243170244244- Seq.Cons (msg, loop)
171171+ | Ok (Incoming.Control_response resp) ->
172172+ handle_control_response t resp;
173173+ loop ()
245174246246- | Ok (Incoming.Control_response resp) ->
247247- handle_control_response t resp;
248248- loop ()
175175+ | Ok (Incoming.Control_request ctrl_req) ->
176176+ Log.info (fun m -> m "Received control request: %s (request_id: %s)"
177177+ (Incoming.Control_request.subtype ctrl_req)
178178+ (Incoming.Control_request.request_id ctrl_req));
179179+ handle_control_request t ctrl_req;
180180+ loop ()
249181250250- | 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 ()
254254- with
255255- | exn ->
256256- Log.err (fun m -> m "Failed to parse message: %s\nLine: %s"
257257- (Printexc.to_string exn) line);
258258- loop ()
182182+ | Error err ->
183183+ Log.err (fun m -> m "Failed to decode incoming message: %s\nLine: %s"
184184+ (Jsont.Error.to_string err) line);
185185+ loop ()
259186 in
260187 Log.debug (fun m -> m "Starting message handler");
261188 loop
···304231 Log.debug (fun m -> m "Registered callback: %s for event: %s" callback_id event_name);
305232 callback_id
306233 ) matcher.Hooks.callbacks in
307307- json_dict [
234234+ json_object [
308235 "matcher", (match matcher.Hooks.matcher with
309236 | 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);
237237+ | None -> json_null ());
238238+ "hookCallbackIds", Jsont.Json.list (List.map (fun id -> json_string id) callback_ids);
312239 ]
313240 ) matchers in
314314- (event_name, Jsont.Array (matchers_json, Jsont.Meta.none)) :: acc
241241+ (event_name, Jsont.Json.list matchers_json) :: acc
315242 ) [] hooks_config in
316243317244 (* Send initialize control request *)
318318- let initialize_msg = json_dict [
245245+ let initialize_msg = json_object [
319246 "type", json_string "control_request";
320247 "request_id", json_string "init_hooks";
321321- "request", json_dict [
248248+ "request", json_object [
322249 "subtype", json_string "initialize";
323323- "hooks", json_dict hooks_json;
250250+ "hooks", json_object hooks_json;
324251 ]
325252 ] in
326253 Log.info (fun m -> m "Sending hooks initialize request");
···420347 let response_json = wait_for_response () in
421348 Log.debug (fun m -> m "Received control response: %s" (json_to_string response_json));
422349423423- (* Parse the response *)
424424- let response_data = find response_json ["response"] in
350350+ (* Parse the response - extract the "response" field using jsont codec *)
351351+ let response_field_codec = Jsont.Object.map ~kind:"ResponseField" Fun.id
352352+ |> Jsont.Object.mem "response" Jsont.json ~enc:Fun.id
353353+ |> Jsont.Object.finish
354354+ in
355355+ let response_data = match Jsont.Json.decode response_field_codec response_json with
356356+ | Ok r -> r
357357+ | Error msg -> raise (Invalid_argument ("Failed to extract response field: " ^ msg))
358358+ in
425359 let response = match Jsont.Json.decode Sdk_control.Response.jsont response_data with
426360 | Ok r -> r
427361 | Error msg -> raise (Invalid_argument ("Failed to decode response: " ^ msg))
+19-38
claudeio/lib/content_block.ml
···47474848module Tool_use = struct
4949 module Input = struct
5050+ (* Dynamic JSON data for tool inputs with typed accessors using jsont decoders *)
5051 type t = Jsont.json
51525253 let jsont = Jsont.json
53545455 let of_string_pairs pairs =
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- )
5656+ Jsont.Json.object' (List.map (fun (k, v) ->
5757+ Jsont.Json.mem (Jsont.Json.name k) (Jsont.Json.string v)
5858+ ) pairs)
61596260 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- )
6161+ Jsont.Json.object' (List.map (fun (k, v) -> Jsont.Json.mem (Jsont.Json.name k) v) assoc)
67626868- 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
6363+ (* Helper to decode an optional field with a given codec *)
6464+ let get_opt (type a) (codec : a Jsont.t) t key : a option =
6565+ let field_codec = Jsont.Object.map ~kind:"field" (fun v -> v)
6666+ |> Jsont.Object.opt_mem key codec ~enc:Fun.id
6767+ |> Jsont.Object.finish
6868+ in
6969+ match Jsont.Json.decode field_codec t with
7070+ | Ok v -> v
7171+ | Error _ -> None
87728888- 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
7373+ let get_string t key = get_opt Jsont.string t key
7474+ let get_int t key = get_opt Jsont.int t key
7575+ let get_bool t key = get_opt Jsont.bool t key
7676+ let get_float t key = get_opt Jsont.number t key
97779878 let keys t =
7979+ (* Decode as object with all members captured as unknown *)
9980 match t with
10081 | Jsont.Object (members, _) -> List.map (fun ((name, _), _) -> name) members
10182 | _ -> []
+25-166
claudeio/lib/hooks.ml
···117117 unknown : Input_unknown.t;
118118 }
119119120120- 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
127127-128128- let get_string json key =
129129- match get_field json key with
130130- | Some (Jsont.String (s, _)) -> Some s
131131- | _ -> None
132132-133133- let of_json json =
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-148120 type t = input
149121150122 let session_id t = t.session_id
···164136 |> Jsont.Object.mem "tool_input" Jsont.json ~enc:tool_input
165137 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
166138 |> Jsont.Object.finish
139139+140140+ let of_json json =
141141+ match Jsont.Json.decode input_jsont json with
142142+ | Ok v -> v
143143+ | Error msg -> raise (Invalid_argument ("PreToolUse: " ^ msg))
167144168145 type permission_decision = [ `Allow | `Deny | `Ask ]
169146···239216 unknown : Input_unknown.t;
240217 }
241218242242- 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
254254-255255- let of_json json =
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-273219 type t = input
274220275221 let session_id t = t.session_id
···291237 |> Jsont.Object.mem "tool_response" Jsont.json ~enc:tool_response
292238 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
293239 |> Jsont.Object.finish
240240+241241+ let of_json json =
242242+ match Jsont.Json.decode input_jsont json with
243243+ | Ok v -> v
244244+ | Error msg -> raise (Invalid_argument ("PostToolUse: " ^ msg))
294245295246 module Output_unknown = struct
296247 type t = Jsont.json
···345296 unknown : Input_unknown.t;
346297 }
347298348348- 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
360360-361361- let of_json json =
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-373299 type t = input
374300375301 let session_id t = t.session_id
···388314 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
389315 |> Jsont.Object.finish
390316317317+ let of_json json =
318318+ match Jsont.Json.decode input_jsont json with
319319+ | Ok v -> v
320320+ | Error msg -> raise (Invalid_argument ("UserPromptSubmit: " ^ msg))
321321+391322 module Output_unknown = struct
392323 type t = Jsont.json
393324 let empty = Jsont.Object ([], Jsont.Meta.none)
···441372 unknown : Input_unknown.t;
442373 }
443374444444- 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
461461-462462- let of_json json =
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-474375 type t = input
475376476377 let session_id t = t.session_id
···488389 |> Jsont.Object.mem "stop_hook_active" Jsont.bool ~enc:stop_hook_active
489390 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
490391 |> Jsont.Object.finish
392392+393393+ let of_json json =
394394+ match Jsont.Json.decode input_jsont json with
395395+ | Ok v -> v
396396+ | Error msg -> raise (Invalid_argument ("Stop: " ^ msg))
491397492398 module Output_unknown = struct
493399 type t = Jsont.json
···524430(** {1 SubagentStop Hook} - Same structure as Stop *)
525431module SubagentStop = struct
526432 include Stop
527527-528528- 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-546546- let of_json json =
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 }
557433end
558434559435(** {1 PreCompact Hook} *)
···571447 unknown : Input_unknown.t;
572448 }
573449574574- 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
586586-587587- let of_json json =
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-596450 type t = input
597451598452 let session_id t = t.session_id
···608462 |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
609463 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
610464 |> Jsont.Object.finish
465465+466466+ let of_json json =
467467+ match Jsont.Json.decode input_jsont json with
468468+ | Ok v -> v
469469+ | Error msg -> raise (Invalid_argument ("PreCompact: " ^ msg))
611470612471 type output = unit (* No specific output for PreCompact *)
613472
+151-18
claudeio/lib/incoming.ml
···11let src = Logs.Src.create "claude.incoming" ~doc:"Incoming messages from Claude CLI"
22module Log = (val Logs.src_log src : Logs.LOG)
3344+(** Control request types for incoming control_request messages *)
55+module Control_request = struct
66+ (** Can use tool permission request *)
77+ module Can_use_tool = struct
88+ type t = {
99+ tool_name : string;
1010+ input : Jsont.json;
1111+ permission_suggestions : Jsont.json list;
1212+ }
1313+1414+ let tool_name t = t.tool_name
1515+ let input t = t.input
1616+ let permission_suggestions t = t.permission_suggestions
1717+1818+ let jsont : t Jsont.t =
1919+ let make tool_name input permission_suggestions =
2020+ { tool_name; input; permission_suggestions = Option.value permission_suggestions ~default:[] }
2121+ in
2222+ Jsont.Object.map ~kind:"CanUseTool" make
2323+ |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
2424+ |> Jsont.Object.mem "input" Jsont.json ~enc:input
2525+ |> Jsont.Object.opt_mem "permission_suggestions" (Jsont.list Jsont.json)
2626+ ~enc:(fun t -> if t.permission_suggestions = [] then None else Some t.permission_suggestions)
2727+ |> Jsont.Object.finish
2828+ end
2929+3030+ (** Hook callback request *)
3131+ module Hook_callback = struct
3232+ type t = {
3333+ callback_id : string;
3434+ input : Jsont.json;
3535+ tool_use_id : string option;
3636+ }
3737+3838+ let callback_id t = t.callback_id
3939+ let input t = t.input
4040+ let tool_use_id t = t.tool_use_id
4141+4242+ let jsont : t Jsont.t =
4343+ let make callback_id input tool_use_id = { callback_id; input; tool_use_id } in
4444+ Jsont.Object.map ~kind:"HookCallback" make
4545+ |> Jsont.Object.mem "callback_id" Jsont.string ~enc:callback_id
4646+ |> Jsont.Object.mem "input" Jsont.json ~enc:input
4747+ |> Jsont.Object.opt_mem "tool_use_id" Jsont.string ~enc:tool_use_id
4848+ |> Jsont.Object.finish
4949+ end
5050+5151+ (** Request payload - discriminated by subtype *)
5252+ type request =
5353+ | Can_use_tool of Can_use_tool.t
5454+ | Hook_callback of Hook_callback.t
5555+ | Unknown of string * Jsont.json
5656+5757+ let request_of_json json =
5858+ let subtype_codec = Jsont.Object.map ~kind:"Subtype" Fun.id
5959+ |> Jsont.Object.mem "subtype" Jsont.string ~enc:Fun.id
6060+ |> Jsont.Object.finish
6161+ in
6262+ match Jsont.Json.decode subtype_codec json with
6363+ | Error _ -> Unknown ("unknown", json)
6464+ | Ok subtype ->
6565+ match subtype with
6666+ | "can_use_tool" ->
6767+ (match Jsont.Json.decode Can_use_tool.jsont json with
6868+ | Ok r -> Can_use_tool r
6969+ | Error _ -> Unknown (subtype, json))
7070+ | "hook_callback" ->
7171+ (match Jsont.Json.decode Hook_callback.jsont json with
7272+ | Ok r -> Hook_callback r
7373+ | Error _ -> Unknown (subtype, json))
7474+ | _ -> Unknown (subtype, json)
7575+7676+ (** Full control request message *)
7777+ type t = {
7878+ request_id : string;
7979+ request : request;
8080+ }
8181+8282+ let request_id t = t.request_id
8383+ let request t = t.request
8484+8585+ let subtype t =
8686+ match t.request with
8787+ | Can_use_tool _ -> "can_use_tool"
8888+ | Hook_callback _ -> "hook_callback"
8989+ | Unknown (s, _) -> s
9090+9191+ let jsont : t Jsont.t =
9292+ let dec json =
9393+ let envelope_codec =
9494+ Jsont.Object.map ~kind:"ControlRequestEnvelope" (fun request_id request_json -> (request_id, request_json))
9595+ |> Jsont.Object.mem "request_id" Jsont.string ~enc:fst
9696+ |> Jsont.Object.mem "request" Jsont.json ~enc:snd
9797+ |> Jsont.Object.finish
9898+ in
9999+ match Jsont.Json.decode envelope_codec json with
100100+ | Error err -> failwith ("Failed to decode control_request envelope: " ^ err)
101101+ | Ok (request_id, request_json) ->
102102+ { request_id; request = request_of_json request_json }
103103+ in
104104+ let enc t =
105105+ let request_json = match t.request with
106106+ | Can_use_tool r ->
107107+ (match Jsont.Json.encode Can_use_tool.jsont r with
108108+ | Ok j -> j
109109+ | Error err -> failwith ("Failed to encode Can_use_tool: " ^ err))
110110+ | Hook_callback r ->
111111+ (match Jsont.Json.encode Hook_callback.jsont r with
112112+ | Ok j -> j
113113+ | Error err -> failwith ("Failed to encode Hook_callback: " ^ err))
114114+ | Unknown (_, j) -> j
115115+ in
116116+ Jsont.Json.object' [
117117+ Jsont.Json.mem (Jsont.Json.name "type") (Jsont.Json.string "control_request");
118118+ Jsont.Json.mem (Jsont.Json.name "request_id") (Jsont.Json.string t.request_id);
119119+ Jsont.Json.mem (Jsont.Json.name "request") request_json;
120120+ ]
121121+ in
122122+ Jsont.map ~kind:"ControlRequest" ~dec ~enc Jsont.json
123123+end
124124+4125type t =
5126 | Message of Message.t
6127 | Control_response of Sdk_control.control_response
128128+ | Control_request of Control_request.t
71298130let jsont : t Jsont.t =
9131 (* Custom decoder that checks the type field and dispatches to the appropriate codec.
1013211133 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
134134+ "system", "result"), while control_response and control_request have single type values.
135135+ Jsont's case_mem discriminator doesn't support multiple tags per case, so we implement
14136 a custom decoder/encoder. *)
15137138138+ let type_field_codec = Jsont.Object.map ~kind:"type_field" Fun.id
139139+ |> Jsont.Object.opt_mem "type" Jsont.string ~enc:Fun.id
140140+ |> Jsont.Object.finish
141141+ in
142142+16143 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" ->
144144+ match Jsont.Json.decode type_field_codec json with
145145+ | Error _ | Ok None ->
146146+ (* No type field, try as message *)
147147+ (match Jsont.Json.decode Message.jsont json with
148148+ | Ok msg -> Message msg
149149+ | Error err -> failwith ("Failed to decode message: " ^ err))
150150+ | Ok (Some typ) ->
151151+ match typ with
152152+ | "control_response" ->
29153 (match Jsont.Json.decode Sdk_control.control_response_jsont json with
30154 | Ok resp -> Control_response resp
31155 | Error err -> failwith ("Failed to decode control_response: " ^ err))
3232- | Some ("user" | "assistant" | "system" | "result") | Some _ | None ->
3333- (* Try to decode as message *)
156156+ | "control_request" ->
157157+ (match Jsont.Json.decode Control_request.jsont json with
158158+ | Ok req -> Control_request req
159159+ | Error err -> failwith ("Failed to decode control_request: " ^ err))
160160+ | "user" | "assistant" | "system" | "result" | _ ->
161161+ (* Message types *)
34162 (match Jsont.Json.decode Message.jsont json with
35163 | Ok msg -> Message msg
3636- | Error err -> failwith ("Failed to decode message: " ^ err)))
3737- | _ -> failwith "Expected JSON object for incoming message"
164164+ | Error err -> failwith ("Failed to decode message: " ^ err))
38165 in
3916640167 let enc = function
···46173 (match Jsont.Json.encode Sdk_control.control_response_jsont resp with
47174 | Ok json -> json
48175 | Error err -> failwith ("Failed to encode control response: " ^ err))
176176+ | Control_request req ->
177177+ (match Jsont.Json.encode Control_request.jsont req with
178178+ | Ok json -> json
179179+ | Error err -> failwith ("Failed to encode control request: " ^ err))
49180 in
5018151182 Jsont.map ~kind:"Incoming" ~dec ~enc Jsont.json
···53184let pp fmt = function
54185 | Message msg -> Format.fprintf fmt "@[<2>Message@ %a@]" Message.pp msg
55186 | Control_response resp -> Format.fprintf fmt "@[<2>ControlResponse@ %a@]" Sdk_control.pp (Sdk_control.Response resp)
187187+ | Control_request req -> Format.fprintf fmt "@[<2>ControlRequest@ { request_id=%S; subtype=%S }@]"
188188+ (Control_request.request_id req) (Control_request.subtype req)
+39-1
claudeio/lib/incoming.mli
···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)
99+ - "control_request" -> Control_request variant
10101111 This provides a clean, type-safe way to decode incoming messages in a single
1212 operation, avoiding the parse-then-switch-then-parse pattern. *)
13131414+(** Control request types for incoming control_request messages *)
1515+module Control_request : sig
1616+ (** Can use tool permission request *)
1717+ module Can_use_tool : sig
1818+ type t
1919+2020+ val tool_name : t -> string
2121+ val input : t -> Jsont.json
2222+ val permission_suggestions : t -> Jsont.json list
2323+ val jsont : t Jsont.t
2424+ end
2525+2626+ (** Hook callback request *)
2727+ module Hook_callback : sig
2828+ type t
2929+3030+ val callback_id : t -> string
3131+ val input : t -> Jsont.json
3232+ val tool_use_id : t -> string option
3333+ val jsont : t Jsont.t
3434+ end
3535+3636+ (** Request payload - discriminated by subtype *)
3737+ type request =
3838+ | Can_use_tool of Can_use_tool.t
3939+ | Hook_callback of Hook_callback.t
4040+ | Unknown of string * Jsont.json
4141+4242+ (** Full control request message *)
4343+ type t
4444+4545+ val request_id : t -> string
4646+ val request : t -> request
4747+ val subtype : t -> string
4848+ val jsont : t Jsont.t
4949+end
5050+1451type t =
1552 | Message of Message.t
1653 | Control_response of Sdk_control.control_response
5454+ | Control_request of Control_request.t
17551856val jsont : t Jsont.t
1957(** Codec for incoming messages. Uses the "type" field to discriminate. *)
+261-233
claudeio/lib/message.ml
···9191 ], Jsont.Meta.none));
9292 ], Jsont.Meta.none)
93939494+ (* Jsont codec for parsing incoming user messages from CLI *)
9595+ let incoming_jsont : t Jsont.t =
9696+ let message_jsont =
9797+ Jsont.Object.map ~kind:"UserMessage" (fun json_content ->
9898+ let content = decode_content json_content in
9999+ { content; unknown = Unknown.empty }
100100+ )
101101+ |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t -> encode_content (content t))
102102+ |> Jsont.Object.finish
103103+ in
104104+ Jsont.Object.map ~kind:"UserEnvelope" Fun.id
105105+ |> Jsont.Object.mem "message" message_jsont ~enc:Fun.id
106106+ |> Jsont.Object.finish
107107+94108 let of_json json =
9595- match json with
9696- | Jsont.Object (fields, _) ->
9797- let message = List.assoc (Jsont.Json.name "message") fields in
9898- let content = match message with
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)
104104- | _ -> raise (Invalid_argument "User.of_json: invalid content"))
105105- | _ -> raise (Invalid_argument "User.of_json: invalid message")
106106- in
107107- { content; unknown = Unknown.empty }
108108- | _ -> raise (Invalid_argument "User.of_json: expected object")
109109+ match Jsont.Json.decode incoming_jsont json with
110110+ | Ok v -> v
111111+ | Error msg -> raise (Invalid_argument ("User.of_json: " ^ msg))
109112110113 let pp fmt t =
111114 match t.content with
···240243 (Jsont.Json.name "message", Jsont.Object (msg_fields, Jsont.Meta.none));
241244 ], Jsont.Meta.none)
242245246246+ (* Jsont codec for parsing incoming assistant messages from CLI *)
247247+ let incoming_jsont : t Jsont.t =
248248+ Jsont.Object.map ~kind:"AssistantEnvelope" Fun.id
249249+ |> Jsont.Object.mem "message" jsont ~enc:Fun.id
250250+ |> Jsont.Object.finish
251251+243252 let of_json json =
244244- match json with
245245- | Jsont.Object (fields, _) ->
246246- let message = List.assoc (Jsont.Json.name "message") fields in
247247- let content, model, error = match message with
248248- | Jsont.Object (msg_fields, _) ->
249249- let content =
250250- match List.assoc (Jsont.Json.name "content") msg_fields with
251251- | Jsont.Array (items, _) -> List.map Content_block.of_json items
252252- | _ -> raise (Invalid_argument "Assistant.of_json: invalid content")
253253- 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
258258- let error =
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")
262262- | None -> None
263263- in
264264- content, model, error
265265- | _ -> raise (Invalid_argument "Assistant.of_json: invalid message")
266266- in
267267- { content; model; error; unknown = Unknown.empty }
268268- | _ -> raise (Invalid_argument "Assistant.of_json: expected object")
253253+ match Jsont.Json.decode incoming_jsont json with
254254+ | Ok v -> v
255255+ | Error msg -> raise (Invalid_argument ("Assistant.of_json: " ^ msg))
269256270257 let pp fmt t =
271258 let text_count = List.length (get_text_blocks t) in
···300287end
301288302289module System = struct
303303- module Data = struct
304304- (* Opaque JSON type with typed accessors *)
305305- type t = Jsont.json
290290+ (** Typed data for system init messages *)
291291+ module Init = struct
292292+ module Unknown = struct
293293+ type t = Jsont.json
294294+ let empty = Jsont.Object ([], Jsont.Meta.none)
295295+ let _jsont = Jsont.json
296296+ end
297297+298298+ type t = {
299299+ session_id : string option;
300300+ model : string option;
301301+ cwd : string option;
302302+ unknown : Unknown.t;
303303+ }
304304+305305+ let make session_id model cwd unknown = { session_id; model; cwd; unknown }
306306307307- let jsont = Jsont.json
307307+ let create ?session_id ?model ?cwd () =
308308+ { session_id; model; cwd; unknown = Unknown.empty }
309309+310310+ let session_id t = t.session_id
311311+ let model t = t.model
312312+ let cwd t = t.cwd
313313+ let unknown t = t.unknown
308314309309- let empty = Jsont.Object ([], Jsont.Meta.none)
315315+ let jsont : t Jsont.t =
316316+ Jsont.Object.map ~kind:"SystemInit" make
317317+ |> Jsont.Object.opt_mem "session_id" Jsont.string ~enc:session_id
318318+ |> Jsont.Object.opt_mem "model" Jsont.string ~enc:model
319319+ |> Jsont.Object.opt_mem "cwd" Jsont.string ~enc:cwd
320320+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
321321+ |> Jsont.Object.finish
322322+ end
310323311311- 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- )
324324+ (** Typed data for system error messages *)
325325+ module Error = struct
326326+ module Unknown = struct
327327+ type t = Jsont.json
328328+ let empty = Jsont.Object ([], Jsont.Meta.none)
329329+ let _jsont = Jsont.json
330330+ end
331331+332332+ type t = {
333333+ error : string;
334334+ unknown : Unknown.t;
335335+ }
336336+337337+ let make error unknown = { error; unknown }
338338+339339+ let create ~error = { error; unknown = Unknown.empty }
340340+341341+ let error t = t.error
342342+ let unknown t = t.unknown
343343+344344+ let jsont : t Jsont.t =
345345+ Jsont.Object.map ~kind:"SystemError" make
346346+ |> Jsont.Object.mem "error" Jsont.string ~enc:error
347347+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
348348+ |> Jsont.Object.finish
349349+ end
316350317317- let get_field t key =
318318- match t with
319319- | Jsont.Object (members, _) ->
320320- List.find_map (fun ((name, _), value) ->
321321- if name = key then Some value else None
322322- ) members
323323- | _ -> None
351351+ (** Sum type for system message data *)
352352+ module Data = struct
353353+ type t =
354354+ | Init of Init.t
355355+ | Error of Error.t
356356+ | Other of Jsont.json (** Unknown subtypes preserve raw JSON *)
324357325325- let get_string t key =
326326- match get_field t key with
327327- | Some (Jsont.String (s, _)) -> Some s
328328- | _ -> None
358358+ let init ?session_id ?model ?cwd () = Init (Init.create ?session_id ?model ?cwd ())
359359+ let error ~error = Error (Error.create ~error)
360360+ let other json = Other json
329361330330- 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
362362+ let session_id = function
363363+ | Init i -> Init.session_id i
335364 | _ -> None
336365337337- let get_bool t key =
338338- match get_field t key with
339339- | Some (Jsont.Bool (b, _)) -> Some b
366366+ let model = function
367367+ | Init i -> Init.model i
340368 | _ -> None
341369342342- let get_float t key =
343343- match get_field t key with
344344- | Some (Jsont.Number (f, _)) -> Some f
370370+ let cwd = function
371371+ | Init i -> Init.cwd i
345372 | _ -> None
346373347347- let get_list t key =
348348- match get_field t key with
349349- | Some (Jsont.Array (items, _)) -> Some items
374374+ let error_msg = function
375375+ | Error e -> Some (Error.error e)
350376 | _ -> None
351377352352- let raw_json t = t
378378+ let to_json = function
379379+ | Init i ->
380380+ (match Jsont.Json.encode Init.jsont i with
381381+ | Ok json -> json
382382+ | Error msg -> failwith ("Init.to_json: " ^ msg))
383383+ | Error e ->
384384+ (match Jsont.Json.encode Error.jsont e with
385385+ | Ok json -> json
386386+ | Error msg -> failwith ("Error.to_json: " ^ msg))
387387+ | Other json -> json
353388354354- let to_json t = t
355355- let of_json json = json
389389+ let of_json ~subtype json =
390390+ match subtype with
391391+ | "init" ->
392392+ (match Jsont.Json.decode Init.jsont json with
393393+ | Ok i -> Init i
394394+ | Error _ -> Other json)
395395+ | "error" ->
396396+ (match Jsont.Json.decode Error.jsont json with
397397+ | Ok e -> Error e
398398+ | Error _ -> Other json)
399399+ | _ -> Other json
356400 end
357401358402 module Unknown = struct
···369413 }
370414371415 let create ~subtype ~data = { subtype; data; unknown = Unknown.empty }
372372- let make subtype data unknown = { subtype; data; unknown }
373416 let subtype t = t.subtype
374417 let data t = t.data
375418 let unknown t = t.unknown
376419420420+ (** Create a system init message *)
421421+ let init ?session_id ?model ?cwd () =
422422+ { subtype = "init";
423423+ data = Data.init ?session_id ?model ?cwd ();
424424+ unknown = Unknown.empty }
425425+426426+ (** Create a system error message *)
427427+ let error ~error =
428428+ { subtype = "error";
429429+ data = Data.error ~error;
430430+ unknown = Unknown.empty }
431431+377432 (* Custom jsont that handles both formats:
378433 - Old format: {"type":"system","subtype":"init","data":{...}}
379434 - New format: {"type":"system","subtype":"init","cwd":"...","session_id":"...",...}
380435 When data field is not present, we use the entire object as data *)
381436 let jsont : t Jsont.t =
382382- let make_with_optional_data subtype opt_data unknown_json =
383383- let data = match opt_data with
384384- | Some d -> d
385385- | None -> unknown_json (* Use the full unknown object as data *)
437437+ let dec json =
438438+ (* First decode just the subtype *)
439439+ let subtype_codec = Jsont.Object.map ~kind:"SystemSubtype" Fun.id
440440+ |> Jsont.Object.mem "subtype" Jsont.string ~enc:Fun.id
441441+ |> Jsont.Object.finish
386442 in
387387- make subtype data Unknown.empty
443443+ match Jsont.Json.decode subtype_codec json with
444444+ | Error msg -> failwith ("System.jsont: " ^ msg)
445445+ | Ok subtype ->
446446+ (* Try to get data field, otherwise use full object *)
447447+ let data_codec = Jsont.Object.map ~kind:"SystemDataField" Fun.id
448448+ |> Jsont.Object.opt_mem "data" Jsont.json ~enc:Fun.id
449449+ |> Jsont.Object.finish
450450+ in
451451+ let data_json = match Jsont.Json.decode data_codec json with
452452+ | Ok (Some d) -> d
453453+ | _ -> json
454454+ in
455455+ let data = Data.of_json ~subtype data_json in
456456+ { subtype; data; unknown = Unknown.empty }
388457 in
389389- Jsont.Object.map ~kind:"System" make_with_optional_data
390390- |> Jsont.Object.mem "subtype" Jsont.string ~enc:subtype
391391- |> Jsont.Object.opt_mem "data" Data.jsont ~enc:(fun t -> Some (data t))
392392- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun _ -> Unknown.empty)
393393- |> Jsont.Object.finish
458458+ let enc t =
459459+ Jsont.Json.object' [
460460+ Jsont.Json.mem (Jsont.Json.name "type") (Jsont.Json.string "system");
461461+ Jsont.Json.mem (Jsont.Json.name "subtype") (Jsont.Json.string t.subtype);
462462+ Jsont.Json.mem (Jsont.Json.name "data") (Data.to_json t.data);
463463+ ]
464464+ in
465465+ Jsont.map ~kind:"System" ~dec ~enc Jsont.json
394466395467 let to_json t =
396396- Jsont.Object ([
397397- (Jsont.Json.name "type", Jsont.String ("system", Jsont.Meta.none));
398398- (Jsont.Json.name "subtype", Jsont.String (t.subtype, Jsont.Meta.none));
399399- (Jsont.Json.name "data", Data.to_json t.data);
400400- ], Jsont.Meta.none)
468468+ match Jsont.Json.encode jsont t with
469469+ | Ok json -> json
470470+ | Error msg -> failwith ("System.to_json: " ^ msg)
401471402472 let of_json json =
403403- match json with
404404- | Jsont.Object (fields, _) ->
405405- let subtype = match List.assoc (Jsont.Json.name "subtype") fields with
406406- | Jsont.String (s, _) -> s
407407- | _ -> raise (Invalid_argument "System.of_json: invalid subtype")
408408- in
409409- let data = Data.of_json (
410410- try List.assoc (Jsont.Json.name "data") fields
411411- with Not_found -> Jsont.Object (fields, Jsont.Meta.none)
412412- ) in
413413- { subtype; data; unknown = Unknown.empty }
414414- | _ -> raise (Invalid_argument "System.of_json: expected object")
473473+ match Jsont.Json.decode jsont json with
474474+ | Ok v -> v
475475+ | Error msg -> raise (Invalid_argument ("System.of_json: " ^ msg))
415476416477 let pp fmt t =
417417- match t.subtype with
418418- | "init" ->
419419- let session_id = Data.get_string t.data "session_id" in
420420- let model = Data.get_string t.data "model" in
421421- let cwd = Data.get_string t.data "cwd" in
478478+ match t.data with
479479+ | Data.Init i ->
422480 Fmt.pf fmt "@[<2>System.init@ { session_id = %a;@ model = %a;@ cwd = %a }@]"
423423- Fmt.(option string) session_id
424424- Fmt.(option string) model
425425- Fmt.(option string) cwd
426426- | "error" ->
427427- let error = Data.get_string t.data "error" in
428428- Fmt.pf fmt "@[<2>System.error@ { error = %a }@]"
429429- Fmt.(option string) error
430430- | _ ->
481481+ Fmt.(option string) (Init.session_id i)
482482+ Fmt.(option string) (Init.model i)
483483+ Fmt.(option string) (Init.cwd i)
484484+ | Data.Error e ->
485485+ Fmt.pf fmt "@[<2>System.error@ { error = %s }@]" (Error.error e)
486486+ | Data.Other _ ->
431487 Fmt.pf fmt "@[<2>System.%s@ { ... }@]" t.subtype
432488end
433489434490module Result = struct
435491 module Usage = struct
436436- (* Opaque JSON type with typed accessors *)
437437- type t = Jsont.json
492492+ module 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
438498439439- let jsont = Jsont.json
499499+ type t = {
500500+ input_tokens : int option;
501501+ output_tokens : int option;
502502+ total_tokens : int option;
503503+ cache_creation_input_tokens : int option;
504504+ cache_read_input_tokens : int option;
505505+ unknown : Unknown.t;
506506+ }
507507+508508+ let make input_tokens output_tokens total_tokens
509509+ cache_creation_input_tokens cache_read_input_tokens unknown =
510510+ { input_tokens; output_tokens; total_tokens;
511511+ cache_creation_input_tokens; cache_read_input_tokens; unknown }
440512441513 let create ?input_tokens ?output_tokens ?total_tokens
442514 ?cache_creation_input_tokens ?cache_read_input_tokens () =
443443- let fields = [] in
444444- let fields = match input_tokens with
445445- | Some n -> (Jsont.Json.name "input_tokens", Jsont.Number (float_of_int n, Jsont.Meta.none)) :: fields
446446- | None -> fields in
447447- let fields = match output_tokens with
448448- | Some n -> (Jsont.Json.name "output_tokens", Jsont.Number (float_of_int n, Jsont.Meta.none)) :: fields
449449- | None -> fields in
450450- let fields = match total_tokens with
451451- | Some n -> (Jsont.Json.name "total_tokens", Jsont.Number (float_of_int n, Jsont.Meta.none)) :: fields
452452- | None -> fields in
453453- let fields = match cache_creation_input_tokens with
454454- | Some n -> (Jsont.Json.name "cache_creation_input_tokens", Jsont.Number (float_of_int n, Jsont.Meta.none)) :: fields
455455- | None -> fields in
456456- let fields = match cache_read_input_tokens with
457457- | Some n -> (Jsont.Json.name "cache_read_input_tokens", Jsont.Number (float_of_int n, Jsont.Meta.none)) :: fields
458458- | None -> fields in
459459- Jsont.Object (fields, Jsont.Meta.none)
460460-461461- let get_field t key =
462462- match t with
463463- | Jsont.Object (members, _) ->
464464- List.find_map (fun ((name, _), value) ->
465465- if name = key then Some value else None
466466- ) members
467467- | _ -> None
468468-469469- let get_int t key =
470470- match get_field t key with
471471- | Some (Jsont.Number (f, _)) ->
472472- let i = int_of_float f in
473473- if float_of_int i = f then Some i else None
474474- | _ -> None
515515+ { input_tokens; output_tokens; total_tokens;
516516+ cache_creation_input_tokens; cache_read_input_tokens;
517517+ unknown = Unknown.empty }
475518476476- let input_tokens t = get_int t "input_tokens"
519519+ let input_tokens t = t.input_tokens
520520+ let output_tokens t = t.output_tokens
521521+ let total_tokens t = t.total_tokens
522522+ let cache_creation_input_tokens t = t.cache_creation_input_tokens
523523+ let cache_read_input_tokens t = t.cache_read_input_tokens
524524+ let unknown t = t.unknown
477525478478- let output_tokens t = get_int t "output_tokens"
479479-480480- let total_tokens t = get_int t "total_tokens"
481481-482482- let cache_creation_input_tokens t = get_int t "cache_creation_input_tokens"
483483-484484- let cache_read_input_tokens t = get_int t "cache_read_input_tokens"
526526+ let jsont : t Jsont.t =
527527+ Jsont.Object.map ~kind:"Usage" make
528528+ |> Jsont.Object.opt_mem "input_tokens" Jsont.int ~enc:input_tokens
529529+ |> Jsont.Object.opt_mem "output_tokens" Jsont.int ~enc:output_tokens
530530+ |> Jsont.Object.opt_mem "total_tokens" Jsont.int ~enc:total_tokens
531531+ |> Jsont.Object.opt_mem "cache_creation_input_tokens" Jsont.int ~enc:cache_creation_input_tokens
532532+ |> Jsont.Object.opt_mem "cache_read_input_tokens" Jsont.int ~enc:cache_read_input_tokens
533533+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
534534+ |> Jsont.Object.finish
485535486536 let effective_input_tokens t =
487487- match input_tokens t with
537537+ match t.input_tokens with
488538 | None -> 0
489539 | Some input ->
490490- let cached = Option.value (cache_read_input_tokens t) ~default:0 in
540540+ let cached = Option.value t.cache_read_input_tokens ~default:0 in
491541 max 0 (input - cached)
492542493543 let total_cost_estimate t ~input_price ~output_price =
494494- match input_tokens t, output_tokens t with
544544+ match t.input_tokens, t.output_tokens with
495545 | Some input, Some output ->
496546 let input_cost = float_of_int input *. input_price /. 1_000_000. in
497547 let output_cost = float_of_int output *. output_price /. 1_000_000. in
···501551 let pp fmt t =
502552 Fmt.pf fmt "@[<2>Usage@ { input = %a;@ output = %a;@ total = %a;@ \
503553 cache_creation = %a;@ cache_read = %a }@]"
504504- Fmt.(option int) (input_tokens t)
505505- Fmt.(option int) (output_tokens t)
506506- Fmt.(option int) (total_tokens t)
507507- Fmt.(option int) (cache_creation_input_tokens t)
508508- Fmt.(option int) (cache_read_input_tokens t)
554554+ Fmt.(option int) t.input_tokens
555555+ Fmt.(option int) t.output_tokens
556556+ Fmt.(option int) t.total_tokens
557557+ Fmt.(option int) t.cache_creation_input_tokens
558558+ Fmt.(option int) t.cache_read_input_tokens
509559510510- let to_json t = t
511511- let of_json json = json
560560+ let to_json t =
561561+ match Jsont.Json.encode jsont t with
562562+ | Ok json -> json
563563+ | Error msg -> failwith ("Usage.to_json: " ^ msg)
564564+565565+ let of_json json =
566566+ match Jsont.Json.decode jsont json with
567567+ | Ok v -> v
568568+ | Error msg -> raise (Invalid_argument ("Usage.of_json: " ^ msg))
512569 end
513570514571 module Unknown = struct
···598655 Jsont.Object (fields, Jsont.Meta.none)
599656600657 let of_json json =
601601- match json with
602602- | Jsont.Object (fields, _) ->
603603- let subtype = match List.assoc (Jsont.Json.name "subtype") fields with
604604- | Jsont.String (s, _) -> s
605605- | _ -> raise (Invalid_argument "Result.of_json: invalid subtype")
606606- in
607607- let duration_ms = match List.assoc (Jsont.Json.name "duration_ms") fields with
608608- | Jsont.Number (f, _) -> int_of_float f
609609- | _ -> raise (Invalid_argument "Result.of_json: invalid duration_ms")
610610- in
611611- let duration_api_ms = match List.assoc (Jsont.Json.name "duration_api_ms") fields with
612612- | Jsont.Number (f, _) -> int_of_float f
613613- | _ -> raise (Invalid_argument "Result.of_json: invalid duration_api_ms")
614614- in
615615- let is_error = match List.assoc (Jsont.Json.name "is_error") fields with
616616- | Jsont.Bool (b, _) -> b
617617- | _ -> raise (Invalid_argument "Result.of_json: invalid is_error")
618618- in
619619- let num_turns = match List.assoc (Jsont.Json.name "num_turns") fields with
620620- | Jsont.Number (f, _) -> int_of_float f
621621- | _ -> raise (Invalid_argument "Result.of_json: invalid num_turns")
622622- in
623623- let session_id = match List.assoc (Jsont.Json.name "session_id") fields with
624624- | Jsont.String (s, _) -> s
625625- | _ -> raise (Invalid_argument "Result.of_json: invalid session_id")
626626- in
627627- let total_cost_usd = match List.assoc_opt (Jsont.Json.name "total_cost_usd") fields with
628628- | Some (Jsont.Number (f, _)) -> Some f
629629- | Some _ -> raise (Invalid_argument "Result.of_json: invalid total_cost_usd")
630630- | None -> None
631631- in
632632- let usage = Option.map Usage.of_json (List.assoc_opt (Jsont.Json.name "usage") fields) in
633633- let result = match List.assoc_opt (Jsont.Json.name "result") fields with
634634- | Some (Jsont.String (s, _)) -> Some s
635635- | Some _ -> raise (Invalid_argument "Result.of_json: invalid result")
636636- | None -> None
637637- in
638638- let structured_output = List.assoc_opt (Jsont.Json.name "structured_output") fields in
639639- { subtype; duration_ms; duration_api_ms; is_error; num_turns;
640640- session_id; total_cost_usd; usage; result; structured_output; unknown = Unknown.empty }
641641- | _ -> raise (Invalid_argument "Result.of_json: expected object")
658658+ match Jsont.Json.decode jsont json with
659659+ | Ok v -> v
660660+ | Error msg -> raise (Invalid_argument ("Result.of_json: " ^ msg))
642661643662 let pp fmt t =
644663 if t.is_error then
···683702684703let system ~subtype ~data = System (System.create ~subtype ~data)
685704let system_init ~session_id =
686686- let data = System.Data.of_assoc [(("session_id", Jsont.String (session_id, Jsont.Meta.none)))] in
687687- System (System.create ~subtype:"init" ~data)
705705+ System (System.init ~session_id ())
688706let system_error ~error =
689689- let data = System.Data.of_assoc [(("error", Jsont.String (error, Jsont.Meta.none)))] in
690690- System (System.create ~subtype:"error" ~data)
707707+ System (System.error ~error)
691708692709let result ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
693710 ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
···700717 | System t -> System.to_json t
701718 | Result t -> Result.to_json t
702719703703-let of_json json =
704704- match json with
705705- | Jsont.Object (fields, _) -> (
706706- match List.assoc_opt (Jsont.Json.name "type") fields with
707707- | Some (Jsont.String ("user", _)) -> User (User.of_json json)
708708- | Some (Jsont.String ("assistant", _)) -> Assistant (Assistant.of_json json)
709709- | Some (Jsont.String ("system", _)) -> System (System.of_json json)
710710- | Some (Jsont.String ("result", _)) -> Result (Result.of_json json)
711711- | Some _ -> raise (Invalid_argument "Message.of_json: invalid type")
712712- | None -> raise (Invalid_argument "Message.of_json: missing type field")
713713- )
714714- | _ -> raise (Invalid_argument "Message.of_json: expected object")
715715-716720(* Jsont codec for the main Message variant type.
717717- Uses a custom decoder to handle both old and new formats. *)
721721+ Uses case_mem for discriminated union based on "type" field. *)
718722let jsont : t Jsont.t =
719719- Jsont.map ~kind:"Message" ~dec:of_json ~enc:to_json Jsont.json
723723+ let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
724724+ let case_user = case_map "user" User.incoming_jsont (fun v -> User v) in
725725+ let case_assistant = case_map "assistant" Assistant.incoming_jsont (fun v -> Assistant v) in
726726+ let case_system = case_map "system" System.jsont (fun v -> System v) in
727727+ let case_result = case_map "result" Result.jsont (fun v -> Result v) in
728728+ let enc_case = function
729729+ | User v -> Jsont.Object.Case.value case_user v
730730+ | Assistant v -> Jsont.Object.Case.value case_assistant v
731731+ | System v -> Jsont.Object.Case.value case_system v
732732+ | Result v -> Jsont.Object.Case.value case_result v
733733+ in
734734+ let cases = Jsont.Object.Case.[
735735+ make case_user;
736736+ make case_assistant;
737737+ make case_system;
738738+ make case_result
739739+ ] in
740740+ Jsont.Object.map ~kind:"Message" Fun.id
741741+ |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
742742+ ~tag_to_string:Fun.id ~tag_compare:String.compare
743743+ |> Jsont.Object.finish
744744+745745+let of_json json =
746746+ match Jsont.Json.decode jsont json with
747747+ | Ok v -> v
748748+ | Error msg -> raise (Invalid_argument ("Message.of_json: " ^ msg))
720749721750let pp fmt = function
722751 | User t -> User.pp fmt t
···746775 | _ -> []
747776748777let get_session_id = function
749749- | System s when System.subtype s = "init" ->
750750- System.Data.get_string (System.data s) "session_id"
778778+ | System s -> System.Data.session_id (System.data s)
751779 | Result r -> Some (Result.session_id r)
752780 | _ -> None
753781
+73-29
claudeio/lib/message.mli
···154154module System : sig
155155 (** System control and status messages. *)
156156157157- module Data : sig
158158- (** System message data. *)
157157+ (** Typed data for system init messages *)
158158+ module Init : sig
159159+ type t
160160+ (** Type of init message data. *)
159161160160- type t = Jsont.json
161161- (** Opaque type for system message data. Contains the raw JSON
162162- with typed accessors for common fields. *)
162162+ val create : ?session_id:string -> ?model:string -> ?cwd:string -> unit -> t
163163+ (** [create ?session_id ?model ?cwd ()] creates init data. *)
164164+165165+ val session_id : t -> string option
166166+ (** [session_id t] returns the session ID if present. *)
167167+168168+ val model : t -> string option
169169+ (** [model t] returns the model name if present. *)
170170+171171+ val cwd : t -> string option
172172+ (** [cwd t] returns the current working directory if present. *)
163173164174 val jsont : t Jsont.t
165165- (** [jsont] is the Jsont codec for system data. *)
175175+ (** [jsont] is the Jsont codec for init data. *)
176176+ end
166177167167- val empty : t
168168- (** [empty] creates empty data. *)
178178+ (** Typed data for system error messages *)
179179+ module Error : sig
180180+ type t
181181+ (** Type of error message data. *)
169182170170- val of_assoc : (string * Jsont.json) list -> t
171171- (** [of_assoc assoc] creates data from an association list. *)
183183+ val create : error:string -> t
184184+ (** [create ~error] creates error data. *)
172185173173- val get_string : t -> string -> string option
174174- (** [get_string t key] returns the string value for [key], if present. *)
186186+ val error : t -> string
187187+ (** [error t] returns the error message. *)
175188176176- val get_int : t -> string -> int option
177177- (** [get_int t key] returns the integer value for [key], if present. *)
189189+ val jsont : t Jsont.t
190190+ (** [jsont] is the Jsont codec for error data. *)
191191+ end
178192179179- val get_bool : t -> string -> bool option
180180- (** [get_bool t key] returns the boolean value for [key], if present. *)
193193+ (** System message data variants *)
194194+ module Data : sig
195195+ type t =
196196+ | Init of Init.t (** Init message data *)
197197+ | Error of Error.t (** Error message data *)
198198+ | Other of Jsont.json (** Unknown subtype data *)
199199+ (** Variant type for system message data. *)
181200182182- val get_float : t -> string -> float option
183183- (** [get_float t key] returns the float value for [key], if present. *)
201201+ val init : ?session_id:string -> ?model:string -> ?cwd:string -> unit -> t
202202+ (** [init ?session_id ?model ?cwd ()] creates init data. *)
184203185185- val get_list : t -> string -> Jsont.json list option
186186- (** [get_list t key] returns the list value for [key], if present. *)
204204+ val error : error:string -> t
205205+ (** [error ~error] creates error data. *)
187206188188- val get_field : t -> string -> Jsont.json option
189189- (** [get_field t key] returns the raw JSON value for [key], if present. *)
207207+ val other : Jsont.json -> t
208208+ (** [other json] creates data for unknown subtypes. *)
190209191191- val raw_json : t -> Jsont.json
192192- (** [raw_json t] returns the full underlying JSON data. *)
210210+ val session_id : t -> string option
211211+ (** [session_id t] extracts session_id from Init data, None otherwise. *)
212212+213213+ val model : t -> string option
214214+ (** [model t] extracts model from Init data, None otherwise. *)
215215+216216+ val cwd : t -> string option
217217+ (** [cwd t] extracts cwd from Init data, None otherwise. *)
218218+219219+ val error_msg : t -> string option
220220+ (** [error_msg t] extracts error from Error data, None otherwise. *)
193221194222 val to_json : t -> Jsont.json
195195- (** [to_json t] converts to JSON representation. Internal use only. *)
223223+ (** [to_json t] converts to JSON representation. *)
196224197197- val of_json : Jsont.json -> t
198198- (** [of_json json] parses from JSON. Internal use only. *)
225225+ val of_json : subtype:string -> Jsont.json -> t
226226+ (** [of_json ~subtype json] parses data based on subtype. *)
199227 end
200228201229 module Unknown : sig
···216244 @param subtype The subtype of the system message
217245 @param data Additional data for the message *)
218246247247+ val init : ?session_id:string -> ?model:string -> ?cwd:string -> unit -> t
248248+ (** [init ?session_id ?model ?cwd ()] creates a system init message. *)
249249+250250+ val error : error:string -> t
251251+ (** [error ~error] creates a system error message. *)
252252+219253 val subtype : t -> string
220254 (** [subtype t] returns the subtype of the system message. *)
221255···244278 module Usage : sig
245279 (** Usage statistics for API calls. *)
246280247247- type t = Jsont.json
248248- (** Opaque type for usage statistics. *)
281281+ module Unknown : sig
282282+ type t = Jsont.json
283283+ val empty : t
284284+ val is_empty : t -> bool
285285+ val jsont : t Jsont.t
286286+ end
287287+288288+ type t
289289+ (** Type for usage statistics. *)
249290250291 val jsont : t Jsont.t
251292 (** [jsont] is the Jsont codec for usage statistics. *)
···274315275316 val cache_read_input_tokens : t -> int option
276317 (** [cache_read_input_tokens t] returns cache read input tokens. *)
318318+319319+ val unknown : t -> Unknown.t
320320+ (** [unknown t] returns the unknown fields preserved from JSON. *)
277321278322 val effective_input_tokens : t -> int
279323 (** [effective_input_tokens t] returns input tokens minus cached tokens, or 0 if not available. *)
+11-10
claudeio/lib/options.ml
···203203 ~enc:Model.to_string
204204 Jsont.string
205205206206-(* Helper codec for env - list of string pairs encoded as object *)
206206+(* Helper codec for env - list of string pairs encoded as object.
207207+ Env is a dynamic object where all values should be strings.
208208+ Uses pattern matching to extract object members, then jsont for string decoding. *)
207209let env_jsont : (string * string) list Jsont.t =
208210 Jsont.map ~kind:"Env"
209209- ~dec:(fun obj ->
210210- match obj with
211211+ ~dec:(fun json ->
212212+ match json with
211213 | Jsont.Object (members, _) ->
212212- List.map (fun ((name, _), value) ->
213213- match value with
214214- | Jsont.String (s, _) -> (name, s)
215215- | _ -> (name, "")
214214+ List.filter_map (fun ((name, _), value) ->
215215+ match Jsont.Json.decode Jsont.string value with
216216+ | Ok s -> Some (name, s)
217217+ | Error _ -> None
216218 ) members
217219 | _ -> [])
218220 ~enc:(fun pairs ->
219219- let mems = List.map (fun (k, v) ->
221221+ Jsont.Json.object' (List.map (fun (k, v) ->
220222 Jsont.Json.mem (Jsont.Json.name k) (Jsont.Json.string v)
221221- ) pairs in
222222- Jsont.Json.object' mems)
223223+ ) pairs))
223224 Jsont.json
224225225226(* Helper codec for headers - list of string pairs encoded as object *)
+18-30
claudeio/test/test_json_utils.ml
···11-(* Helper functions for JSON operations in tests *)
11+(* Helper functions for JSON operations in tests using jsont codecs *)
2233let to_string ?(minify=false) json =
44 let format = if minify then Jsont.Minify else Jsont.Indent in
···66 | Ok s -> s
77 | Error err -> Jsont.Error.to_string err
8899-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
99+(* Helper to decode an optional field with a given codec *)
1010+let get_opt (type a) (codec : a Jsont.t) json key : a option =
1111+ let field_codec = Jsont.Object.map ~kind:"field" (fun v -> v)
1212+ |> Jsont.Object.opt_mem key codec ~enc:Fun.id
1313+ |> Jsont.Object.finish
1414+ in
1515+ match Jsont.Json.decode field_codec json with
1616+ | Ok v -> v
1717+ | Error _ -> None
28182929-let get_bool json key =
3030- match get_field json key with
3131- | Some (Jsont.Bool (b, _)) -> Some b
3232- | _ -> None
1919+let get_string json key = get_opt Jsont.string json key
2020+let get_int json key = get_opt Jsont.int json key
2121+let get_bool json key = get_opt Jsont.bool json key
33223423let get_array json key =
3535- match get_field json key with
3636- | Some (Jsont.Array (items, _)) -> Some items
3737- | _ -> None
2424+ get_opt (Jsont.list Jsont.json) json key
38253939-let as_string = function
4040- | Jsont.String (s, _) -> Some s
4141- | _ -> None
2626+let as_string json =
2727+ match Jsont.Json.decode Jsont.string json with
2828+ | Ok s -> Some s
2929+ | Error _ -> None