···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Error handling for claudeio. *)
77-88-type t =
99- | Cli_not_found of string
1010- | Process_error of string
1111- | Connection_error of string
1212- | Protocol_error of string
1313- | Timeout of string
1414- | Permission_denied of { tool_name : string; message : string }
1515- | Hook_error of { callback_id : string; message : string }
1616- | Control_error of { request_id : string; message : string }
1717-1818-exception E of t
1919-2020-val pp : Format.formatter -> t -> unit
2121-(** Pretty-print an error. *)
2222-2323-val to_string : t -> string
2424-(** Convert error to string. *)
2525-2626-val raise : t -> 'a
2727-(** [raise err] raises [E err]. *)
2828-2929-(** {1 Convenience Raisers} *)
3030-3131-val cli_not_found : string -> 'a
3232-val process_error : string -> 'a
3333-val connection_error : string -> 'a
3434-val protocol_error : string -> 'a
3535-val timeout : string -> 'a
3636-val permission_denied : tool_name:string -> message:string -> 'a
3737-val hook_error : callback_id:string -> message:string -> 'a
3838-val control_error : request_id:string -> message:string -> 'a
3939-4040-(** {1 Result Helpers} *)
4141-4242-val get_ok : msg:string -> ('a, string) result -> 'a
4343-(** [get_ok ~msg result] returns the Ok value or raises Protocol_error with msg
4444- prefix. *)
4545-4646-val get_ok' : msg:string -> ('a, string) result -> 'a
4747-(** [get_ok' ~msg result] returns the Ok value or raises Protocol_error with
4848- string error. *)
-490
lib/sdk_control.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-let src =
77- Logs.Src.create "claude.sdk_control" ~doc:"Claude SDK control protocol"
88-99-module Log = (val Logs.src_log src : Logs.LOG)
1010-1111-module Request = struct
1212- type interrupt = { subtype : [ `Interrupt ]; unknown : Unknown.t }
1313-1414- type permission = {
1515- subtype : [ `Can_use_tool ];
1616- tool_name : string;
1717- input : Jsont.json;
1818- permission_suggestions : Proto.Permissions.Update.t list option;
1919- blocked_path : string option;
2020- unknown : Unknown.t;
2121- }
2222-2323- type initialize = {
2424- subtype : [ `Initialize ];
2525- hooks : (string * Jsont.json) list option;
2626- unknown : Unknown.t;
2727- }
2828-2929- type set_permission_mode = {
3030- subtype : [ `Set_permission_mode ];
3131- mode : Proto.Permissions.Mode.t;
3232- unknown : Unknown.t;
3333- }
3434-3535- type hook_callback = {
3636- subtype : [ `Hook_callback ];
3737- callback_id : string;
3838- input : Jsont.json;
3939- tool_use_id : string option;
4040- unknown : Unknown.t;
4141- }
4242-4343- type mcp_message = {
4444- subtype : [ `Mcp_message ];
4545- server_name : string;
4646- message : Jsont.json;
4747- unknown : Unknown.t;
4848- }
4949-5050- type set_model = {
5151- subtype : [ `Set_model ];
5252- model : string;
5353- unknown : Unknown.t;
5454- }
5555-5656- type get_server_info = { subtype : [ `Get_server_info ]; unknown : Unknown.t }
5757-5858- type t =
5959- | Interrupt of interrupt
6060- | Permission of permission
6161- | Initialize of initialize
6262- | Set_permission_mode of set_permission_mode
6363- | Hook_callback of hook_callback
6464- | Mcp_message of mcp_message
6565- | Set_model of set_model
6666- | Get_server_info of get_server_info
6767-6868- let interrupt ?(unknown = Unknown.empty) () =
6969- Interrupt { subtype = `Interrupt; unknown }
7070-7171- let permission ~tool_name ~input ?permission_suggestions ?blocked_path
7272- ?(unknown = Unknown.empty) () =
7373- Permission
7474- {
7575- subtype = `Can_use_tool;
7676- tool_name;
7777- input;
7878- permission_suggestions;
7979- blocked_path;
8080- unknown;
8181- }
8282-8383- let initialize ?hooks ?(unknown = Unknown.empty) () =
8484- Initialize { subtype = `Initialize; hooks; unknown }
8585-8686- let set_permission_mode ~mode ?(unknown = Unknown.empty) () =
8787- Set_permission_mode { subtype = `Set_permission_mode; mode; unknown }
8888-8989- let hook_callback ~callback_id ~input ?tool_use_id ?(unknown = Unknown.empty)
9090- () =
9191- Hook_callback
9292- { subtype = `Hook_callback; callback_id; input; tool_use_id; unknown }
9393-9494- let mcp_message ~server_name ~message ?(unknown = Unknown.empty) () =
9595- Mcp_message { subtype = `Mcp_message; server_name; message; unknown }
9696-9797- let set_model ~model ?(unknown = Unknown.empty) () =
9898- Set_model { subtype = `Set_model; model; unknown }
9999-100100- let get_server_info ?(unknown = Unknown.empty) () =
101101- Get_server_info { subtype = `Get_server_info; unknown }
102102-103103- (* Individual record codecs *)
104104- let interrupt_jsont : interrupt Jsont.t =
105105- let make (unknown : Unknown.t) : interrupt =
106106- { subtype = `Interrupt; unknown }
107107- in
108108- Jsont.Object.map ~kind:"Interrupt" make
109109- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : interrupt) ->
110110- r.unknown)
111111- |> Jsont.Object.finish
112112-113113- let permission_jsont : permission Jsont.t =
114114- let make tool_name input permission_suggestions blocked_path
115115- (unknown : Unknown.t) : permission =
116116- {
117117- subtype = `Can_use_tool;
118118- tool_name;
119119- input;
120120- permission_suggestions;
121121- blocked_path;
122122- unknown;
123123- }
124124- in
125125- Jsont.Object.map ~kind:"Permission" make
126126- |> Jsont.Object.mem "tool_name" Jsont.string ~enc:(fun (r : permission) ->
127127- r.tool_name)
128128- |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : permission) ->
129129- r.input)
130130- |> Jsont.Object.opt_mem "permission_suggestions"
131131- (Jsont.list Proto.Permissions.Update.jsont)
132132- ~enc:(fun (r : permission) -> r.permission_suggestions)
133133- |> Jsont.Object.opt_mem "blocked_path" Jsont.string
134134- ~enc:(fun (r : permission) -> r.blocked_path)
135135- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : permission) ->
136136- r.unknown)
137137- |> Jsont.Object.finish
138138-139139- let initialize_jsont : initialize Jsont.t =
140140- (* The hooks field is an object with string keys and json values *)
141141- let hooks_map_jsont = Jsont.Object.as_string_map Jsont.json in
142142- let module StringMap = Map.Make (String) in
143143- let hooks_jsont =
144144- Jsont.map
145145- ~dec:(fun m -> StringMap.bindings m)
146146- ~enc:(fun l -> StringMap.of_seq (List.to_seq l))
147147- hooks_map_jsont
148148- in
149149- let make hooks (unknown : Unknown.t) : initialize =
150150- { subtype = `Initialize; hooks; unknown }
151151- in
152152- Jsont.Object.map ~kind:"Initialize" make
153153- |> Jsont.Object.opt_mem "hooks" hooks_jsont ~enc:(fun (r : initialize) ->
154154- r.hooks)
155155- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : initialize) ->
156156- r.unknown)
157157- |> Jsont.Object.finish
158158-159159- let set_permission_mode_jsont : set_permission_mode Jsont.t =
160160- let make mode (unknown : Unknown.t) : set_permission_mode =
161161- { subtype = `Set_permission_mode; mode; unknown }
162162- in
163163- Jsont.Object.map ~kind:"SetPermissionMode" make
164164- |> Jsont.Object.mem "mode" Proto.Permissions.Mode.jsont
165165- ~enc:(fun (r : set_permission_mode) -> r.mode)
166166- |> Jsont.Object.keep_unknown Jsont.json_mems
167167- ~enc:(fun (r : set_permission_mode) -> r.unknown)
168168- |> Jsont.Object.finish
169169-170170- let hook_callback_jsont : hook_callback Jsont.t =
171171- let make callback_id input tool_use_id (unknown : Unknown.t) : hook_callback
172172- =
173173- { subtype = `Hook_callback; callback_id; input; tool_use_id; unknown }
174174- in
175175- Jsont.Object.map ~kind:"HookCallback" make
176176- |> Jsont.Object.mem "callback_id" Jsont.string
177177- ~enc:(fun (r : hook_callback) -> r.callback_id)
178178- |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : hook_callback) ->
179179- r.input)
180180- |> Jsont.Object.opt_mem "tool_use_id" Jsont.string
181181- ~enc:(fun (r : hook_callback) -> r.tool_use_id)
182182- |> Jsont.Object.keep_unknown Jsont.json_mems
183183- ~enc:(fun (r : hook_callback) -> r.unknown)
184184- |> Jsont.Object.finish
185185-186186- let mcp_message_jsont : mcp_message Jsont.t =
187187- let make server_name message (unknown : Unknown.t) : mcp_message =
188188- { subtype = `Mcp_message; server_name; message; unknown }
189189- in
190190- Jsont.Object.map ~kind:"McpMessage" make
191191- |> Jsont.Object.mem "server_name" Jsont.string
192192- ~enc:(fun (r : mcp_message) -> r.server_name)
193193- |> Jsont.Object.mem "message" Jsont.json ~enc:(fun (r : mcp_message) ->
194194- r.message)
195195- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : mcp_message) ->
196196- r.unknown)
197197- |> Jsont.Object.finish
198198-199199- let set_model_jsont : set_model Jsont.t =
200200- let make model (unknown : Unknown.t) : set_model =
201201- { subtype = `Set_model; model; unknown }
202202- in
203203- Jsont.Object.map ~kind:"SetModel" make
204204- |> Jsont.Object.mem "model" Jsont.string ~enc:(fun (r : set_model) ->
205205- r.model)
206206- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : set_model) ->
207207- r.unknown)
208208- |> Jsont.Object.finish
209209-210210- let get_server_info_jsont : get_server_info Jsont.t =
211211- let make (unknown : Unknown.t) : get_server_info =
212212- { subtype = `Get_server_info; unknown }
213213- in
214214- Jsont.Object.map ~kind:"GetServerInfo" make
215215- |> Jsont.Object.keep_unknown Jsont.json_mems
216216- ~enc:(fun (r : get_server_info) -> r.unknown)
217217- |> Jsont.Object.finish
218218-219219- (* Main variant codec using subtype discriminator *)
220220- let jsont : t Jsont.t =
221221- let case_interrupt =
222222- Jsont.Object.Case.map "interrupt" interrupt_jsont ~dec:(fun v ->
223223- Interrupt v)
224224- in
225225- let case_permission =
226226- Jsont.Object.Case.map "can_use_tool" permission_jsont ~dec:(fun v ->
227227- Permission v)
228228- in
229229- let case_initialize =
230230- Jsont.Object.Case.map "initialize" initialize_jsont ~dec:(fun v ->
231231- Initialize v)
232232- in
233233- let case_set_permission_mode =
234234- Jsont.Object.Case.map "set_permission_mode" set_permission_mode_jsont
235235- ~dec:(fun v -> Set_permission_mode v)
236236- in
237237- let case_hook_callback =
238238- Jsont.Object.Case.map "hook_callback" hook_callback_jsont ~dec:(fun v ->
239239- Hook_callback v)
240240- in
241241- let case_mcp_message =
242242- Jsont.Object.Case.map "mcp_message" mcp_message_jsont ~dec:(fun v ->
243243- Mcp_message v)
244244- in
245245- let case_set_model =
246246- Jsont.Object.Case.map "set_model" set_model_jsont ~dec:(fun v ->
247247- Set_model v)
248248- in
249249- let case_get_server_info =
250250- Jsont.Object.Case.map "get_server_info" get_server_info_jsont
251251- ~dec:(fun v -> Get_server_info v)
252252- in
253253-254254- let enc_case = function
255255- | Interrupt v -> Jsont.Object.Case.value case_interrupt v
256256- | Permission v -> Jsont.Object.Case.value case_permission v
257257- | Initialize v -> Jsont.Object.Case.value case_initialize v
258258- | Set_permission_mode v ->
259259- Jsont.Object.Case.value case_set_permission_mode v
260260- | Hook_callback v -> Jsont.Object.Case.value case_hook_callback v
261261- | Mcp_message v -> Jsont.Object.Case.value case_mcp_message v
262262- | Set_model v -> Jsont.Object.Case.value case_set_model v
263263- | Get_server_info v -> Jsont.Object.Case.value case_get_server_info v
264264- in
265265-266266- let cases =
267267- Jsont.Object.Case.
268268- [
269269- make case_interrupt;
270270- make case_permission;
271271- make case_initialize;
272272- make case_set_permission_mode;
273273- make case_hook_callback;
274274- make case_mcp_message;
275275- make case_set_model;
276276- make case_get_server_info;
277277- ]
278278- in
279279-280280- Jsont.Object.map ~kind:"Request" Fun.id
281281- |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
282282- ~tag_to_string:Fun.id ~tag_compare:String.compare
283283- |> Jsont.Object.finish
284284-end
285285-286286-module Response = struct
287287- (* Re-export Error_code from Proto *)
288288- module Error_code = Proto.Control.Response.Error_code
289289-290290- (* Structured error similar to JSON-RPC *)
291291- type error_detail = { code : int; message : string; data : Jsont.json option }
292292-293293- let error_detail ~code ~message ?data () =
294294- { code = Error_code.to_int code; message; data }
295295-296296- let error_detail_jsont : error_detail Jsont.t =
297297- let make code message data = { code; message; data } in
298298- Jsont.Object.map ~kind:"ErrorDetail" make
299299- |> Jsont.Object.mem "code" Jsont.int ~enc:(fun e -> e.code)
300300- |> Jsont.Object.mem "message" Jsont.string ~enc:(fun e -> e.message)
301301- |> Jsont.Object.opt_mem "data" Jsont.json ~enc:(fun e -> e.data)
302302- |> Jsont.Object.finish
303303-304304- type success = {
305305- subtype : [ `Success ];
306306- request_id : string;
307307- response : Jsont.json option;
308308- unknown : Unknown.t;
309309- }
310310-311311- type error = {
312312- subtype : [ `Error ];
313313- request_id : string;
314314- error : error_detail;
315315- unknown : Unknown.t;
316316- }
317317-318318- type t = Success of success | Error of error
319319-320320- let success ~request_id ?response ?(unknown = Unknown.empty) () =
321321- Success { subtype = `Success; request_id; response; unknown }
322322-323323- let error ~request_id ~error ?(unknown = Unknown.empty) () =
324324- Error { subtype = `Error; request_id; error; unknown }
325325-326326- (* Individual record codecs *)
327327- let success_jsont : success Jsont.t =
328328- let make request_id response (unknown : Unknown.t) : success =
329329- { subtype = `Success; request_id; response; unknown }
330330- in
331331- Jsont.Object.map ~kind:"Success" make
332332- |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun (r : success) ->
333333- r.request_id)
334334- |> Jsont.Object.opt_mem "response" Jsont.json ~enc:(fun (r : success) ->
335335- r.response)
336336- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : success) ->
337337- r.unknown)
338338- |> Jsont.Object.finish
339339-340340- let error_jsont : error Jsont.t =
341341- let make request_id error (unknown : Unknown.t) : error =
342342- { subtype = `Error; request_id; error; unknown }
343343- in
344344- Jsont.Object.map ~kind:"Error" make
345345- |> Jsont.Object.mem "request_id" Jsont.string ~enc:(fun (r : error) ->
346346- r.request_id)
347347- |> Jsont.Object.mem "error" error_detail_jsont ~enc:(fun (r : error) ->
348348- r.error)
349349- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : error) ->
350350- r.unknown)
351351- |> Jsont.Object.finish
352352-353353- (* Main variant codec using subtype discriminator *)
354354- let jsont : t Jsont.t =
355355- let case_success =
356356- Jsont.Object.Case.map "success" success_jsont ~dec:(fun v -> Success v)
357357- in
358358- let case_error =
359359- Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v)
360360- in
361361-362362- let enc_case = function
363363- | Success v -> Jsont.Object.Case.value case_success v
364364- | Error v -> Jsont.Object.Case.value case_error v
365365- in
366366-367367- let cases = Jsont.Object.Case.[ make case_success; make case_error ] in
368368-369369- Jsont.Object.map ~kind:"Response" Fun.id
370370- |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
371371- ~tag_to_string:Fun.id ~tag_compare:String.compare
372372- |> Jsont.Object.finish
373373-end
374374-375375-type control_request = {
376376- type_ : [ `Control_request ];
377377- request_id : string;
378378- request : Request.t;
379379- unknown : Unknown.t;
380380-}
381381-382382-type control_response = {
383383- type_ : [ `Control_response ];
384384- response : Response.t;
385385- unknown : Unknown.t;
386386-}
387387-388388-type t = Request of control_request | Response of control_response
389389-390390-let create_request ~request_id ~request ?(unknown = Unknown.empty) () =
391391- Request { type_ = `Control_request; request_id; request; unknown }
392392-393393-let create_response ~response ?(unknown = Unknown.empty) () =
394394- Response { type_ = `Control_response; response; unknown }
395395-396396-(* Individual record codecs *)
397397-let control_request_jsont : control_request Jsont.t =
398398- let make request_id request (unknown : Unknown.t) : control_request =
399399- { type_ = `Control_request; request_id; request; unknown }
400400- in
401401- Jsont.Object.map ~kind:"ControlRequest" make
402402- |> Jsont.Object.mem "request_id" Jsont.string
403403- ~enc:(fun (r : control_request) -> r.request_id)
404404- |> Jsont.Object.mem "request" Request.jsont ~enc:(fun (r : control_request) ->
405405- r.request)
406406- |> Jsont.Object.keep_unknown Jsont.json_mems
407407- ~enc:(fun (r : control_request) -> r.unknown)
408408- |> Jsont.Object.finish
409409-410410-let control_response_jsont : control_response Jsont.t =
411411- let make response (unknown : Unknown.t) : control_response =
412412- { type_ = `Control_response; response; unknown }
413413- in
414414- Jsont.Object.map ~kind:"ControlResponse" make
415415- |> Jsont.Object.mem "response" Response.jsont
416416- ~enc:(fun (r : control_response) -> r.response)
417417- |> Jsont.Object.keep_unknown Jsont.json_mems
418418- ~enc:(fun (r : control_response) -> r.unknown)
419419- |> Jsont.Object.finish
420420-421421-(* Main variant codec using type discriminator *)
422422-let jsont : t Jsont.t =
423423- let case_request =
424424- Jsont.Object.Case.map "control_request" control_request_jsont ~dec:(fun v ->
425425- Request v)
426426- in
427427- let case_response =
428428- Jsont.Object.Case.map "control_response" control_response_jsont
429429- ~dec:(fun v -> Response v)
430430- in
431431-432432- let enc_case = function
433433- | Request v -> Jsont.Object.Case.value case_request v
434434- | Response v -> Jsont.Object.Case.value case_response v
435435- in
436436-437437- let cases = Jsont.Object.Case.[ make case_request; make case_response ] in
438438-439439- Jsont.Object.map ~kind:"Control" Fun.id
440440- |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
441441- ~tag_to_string:Fun.id ~tag_compare:String.compare
442442- |> Jsont.Object.finish
443443-444444-let log_request req =
445445- Log.debug (fun m ->
446446- m "SDK control request: %a" (Jsont.pp_value Request.jsont ()) req)
447447-448448-let log_response resp =
449449- Log.debug (fun m ->
450450- m "SDK control response: %a" (Jsont.pp_value Response.jsont ()) resp)
451451-452452-(** Server information *)
453453-module Server_info = struct
454454- type t = {
455455- version : string;
456456- capabilities : string list;
457457- commands : string list;
458458- output_styles : string list;
459459- unknown : Unknown.t;
460460- }
461461-462462- let create ~version ~capabilities ~commands ~output_styles
463463- ?(unknown = Unknown.empty) () =
464464- { version; capabilities; commands; output_styles; unknown }
465465-466466- let version t = t.version
467467- let capabilities t = t.capabilities
468468- let commands t = t.commands
469469- let output_styles t = t.output_styles
470470- let unknown t = t.unknown
471471-472472- let jsont : t Jsont.t =
473473- let make version capabilities commands output_styles (unknown : Unknown.t) :
474474- t =
475475- { version; capabilities; commands; output_styles; unknown }
476476- in
477477- Jsont.Object.map ~kind:"ServerInfo" make
478478- |> Jsont.Object.mem "version" Jsont.string ~enc:(fun (r : t) -> r.version)
479479- |> Jsont.Object.mem "capabilities" (Jsont.list Jsont.string)
480480- ~enc:(fun (r : t) -> r.capabilities)
481481- ~dec_absent:[]
482482- |> Jsont.Object.mem "commands" (Jsont.list Jsont.string)
483483- ~enc:(fun (r : t) -> r.commands)
484484- ~dec_absent:[]
485485- |> Jsont.Object.mem "outputStyles" (Jsont.list Jsont.string)
486486- ~enc:(fun (r : t) -> r.output_styles)
487487- ~dec_absent:[]
488488- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : t) -> r.unknown)
489489- |> Jsont.Object.finish
490490-end
-369
lib/sdk_control.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** SDK Control Protocol for Claude.
77-88- This module defines the typed SDK control protocol for bidirectional
99- communication between the SDK and the Claude CLI. It handles:
1010-1111- - Permission requests (tool usage authorization)
1212- - Hook callbacks (intercepting and modifying tool execution)
1313- - Dynamic control (changing settings mid-conversation)
1414- - Server introspection (querying capabilities)
1515-1616- {2 Protocol Overview}
1717-1818- The SDK control protocol is a JSON-based request/response protocol that runs
1919- alongside the main message stream. It enables:
2020-2121- 1. {b Callbacks}: Claude asks the SDK for permission or hook execution 2.
2222- {b Control}: SDK changes Claude's behavior dynamically 3. {b Introspection}:
2323- SDK queries server metadata
2424-2525- {2 Request/Response Flow}
2626-2727- {v
2828- SDK Claude CLI
2929- | |
3030- |-- Initialize (with hooks) --> |
3131- |<-- Permission Request --------| (for tool usage)
3232- |-- Allow/Deny Response ------> |
3333- | |
3434- |<-- Hook Callback -------------| (pre/post tool)
3535- |-- Hook Result -------------> |
3636- | |
3737- |-- Set Model ---------------> | (dynamic control)
3838- |<-- Success Response ----------|
3939- | |
4040- |-- Get Server Info ----------> |
4141- |<-- Server Info Response ------|
4242- v}
4343-4444- {2 Usage}
4545-4646- Most users won't interact with this module directly. The {!Client} module
4747- handles the protocol automatically. However, this module is exposed for:
4848-4949- - Understanding the control protocol
5050- - Implementing custom control logic
5151- - Debugging control message flow
5252- - Advanced SDK extensions
5353-5454- {2 Dynamic Control Examples}
5555-5656- See {!Client.set_permission_mode}, {!Client.set_model}, and
5757- {!Client.get_server_info} for high-level APIs that use this protocol. *)
5858-5959-val src : Logs.Src.t
6060-(** The log source for SDK control operations *)
6161-6262-(** {1 Request Types} *)
6363-6464-module Request : sig
6565- (** SDK control request types. *)
6666-6767- type interrupt = { subtype : [ `Interrupt ]; unknown : Unknown.t }
6868- (** Interrupt request to stop execution. *)
6969-7070- type permission = {
7171- subtype : [ `Can_use_tool ];
7272- tool_name : string;
7373- input : Jsont.json;
7474- permission_suggestions : Proto.Permissions.Update.t list option;
7575- blocked_path : string option;
7676- unknown : Unknown.t;
7777- }
7878- (** Permission request for tool usage. *)
7979-8080- type initialize = {
8181- subtype : [ `Initialize ];
8282- hooks : (string * Jsont.json) list option; (* Hook event to configuration *)
8383- unknown : Unknown.t;
8484- }
8585- (** Initialize request with optional hook configuration. *)
8686-8787- type set_permission_mode = {
8888- subtype : [ `Set_permission_mode ];
8989- mode : Proto.Permissions.Mode.t;
9090- unknown : Unknown.t;
9191- }
9292- (** Request to change permission mode. *)
9393-9494- type hook_callback = {
9595- subtype : [ `Hook_callback ];
9696- callback_id : string;
9797- input : Jsont.json;
9898- tool_use_id : string option;
9999- unknown : Unknown.t;
100100- }
101101- (** Hook callback request. *)
102102-103103- type mcp_message = {
104104- subtype : [ `Mcp_message ];
105105- server_name : string;
106106- message : Jsont.json;
107107- unknown : Unknown.t;
108108- }
109109- (** MCP server message request. *)
110110-111111- type set_model = {
112112- subtype : [ `Set_model ];
113113- model : string;
114114- unknown : Unknown.t;
115115- }
116116- (** Request to change the AI model. *)
117117-118118- type get_server_info = { subtype : [ `Get_server_info ]; unknown : Unknown.t }
119119- (** Request to get server information. *)
120120-121121- type t =
122122- | Interrupt of interrupt
123123- | Permission of permission
124124- | Initialize of initialize
125125- | Set_permission_mode of set_permission_mode
126126- | Hook_callback of hook_callback
127127- | Mcp_message of mcp_message
128128- | Set_model of set_model
129129- | Get_server_info of get_server_info
130130- (** The type of SDK control requests. *)
131131-132132- val interrupt : ?unknown:Unknown.t -> unit -> t
133133- (** [interrupt ?unknown ()] creates an interrupt request. *)
134134-135135- val permission :
136136- tool_name:string ->
137137- input:Jsont.json ->
138138- ?permission_suggestions:Proto.Permissions.Update.t list ->
139139- ?blocked_path:string ->
140140- ?unknown:Unknown.t ->
141141- unit ->
142142- t
143143- (** [permission ~tool_name ~input ?permission_suggestions ?blocked_path
144144- ?unknown ()] creates a permission request. *)
145145-146146- val initialize :
147147- ?hooks:(string * Jsont.json) list -> ?unknown:Unknown.t -> unit -> t
148148- (** [initialize ?hooks ?unknown ()] creates an initialize request. *)
149149-150150- val set_permission_mode :
151151- mode:Proto.Permissions.Mode.t -> ?unknown:Unknown.t -> unit -> t
152152- (** [set_permission_mode ~mode ?unknown] creates a permission mode change
153153- request. *)
154154-155155- val hook_callback :
156156- callback_id:string ->
157157- input:Jsont.json ->
158158- ?tool_use_id:string ->
159159- ?unknown:Unknown.t ->
160160- unit ->
161161- t
162162- (** [hook_callback ~callback_id ~input ?tool_use_id ?unknown ()] creates a
163163- hook callback request. *)
164164-165165- val mcp_message :
166166- server_name:string -> message:Jsont.json -> ?unknown:Unknown.t -> unit -> t
167167- (** [mcp_message ~server_name ~message ?unknown] creates an MCP message
168168- request. *)
169169-170170- val set_model : model:string -> ?unknown:Unknown.t -> unit -> t
171171- (** [set_model ~model ?unknown] creates a model change request. *)
172172-173173- val get_server_info : ?unknown:Unknown.t -> unit -> t
174174- (** [get_server_info ?unknown ()] creates a server info request. *)
175175-176176- val jsont : t Jsont.t
177177- (** [jsont] is the jsont codec for requests. Use [Jsont.pp_value jsont ()] for
178178- pretty-printing. *)
179179-end
180180-181181-(** {1 Response Types} *)
182182-183183-module Response : sig
184184- (** SDK control response types. *)
185185-186186- module Error_code = Proto.Control.Response.Error_code
187187- (** Re-export Error_code from Proto for convenience. *)
188188-189189- type error_detail = {
190190- code : int; (** Error code for programmatic handling *)
191191- message : string; (** Human-readable error message *)
192192- data : Jsont.json option; (** Optional additional error data *)
193193- }
194194- (** Structured error detail similar to JSON-RPC.
195195-196196- This allows programmatic error handling with numeric error codes and
197197- optional structured data for additional context. *)
198198-199199- val error_detail :
200200- code:[< Error_code.t ] ->
201201- message:string ->
202202- ?data:Jsont.json ->
203203- unit ->
204204- error_detail
205205- (** [error_detail ~code ~message ?data ()] creates a structured error detail
206206- using typed error codes.
207207-208208- Example:
209209- {[
210210- error_detail ~code:`Method_not_found ~message:"Hook callback not found"
211211- ()
212212- ]} *)
213213-214214- val error_detail_jsont : error_detail Jsont.t
215215- (** [error_detail_jsont] is the Jsont codec for error details. *)
216216-217217- type success = {
218218- subtype : [ `Success ];
219219- request_id : string;
220220- response : Jsont.json option;
221221- unknown : Unknown.t;
222222- }
223223- (** Successful response. *)
224224-225225- type error = {
226226- subtype : [ `Error ];
227227- request_id : string;
228228- error : error_detail;
229229- unknown : Unknown.t;
230230- }
231231- (** Error response with structured error detail. *)
232232-233233- type t =
234234- | Success of success
235235- | Error of error (** The type of SDK control responses. *)
236236-237237- val success :
238238- request_id:string -> ?response:Jsont.json -> ?unknown:Unknown.t -> unit -> t
239239- (** [success ~request_id ?response ?unknown ()] creates a success response. *)
240240-241241- val error :
242242- request_id:string -> error:error_detail -> ?unknown:Unknown.t -> unit -> t
243243- (** [error ~request_id ~error ?unknown] creates an error response with
244244- structured error detail. *)
245245-246246- val jsont : t Jsont.t
247247- (** [jsont] is the jsont codec for responses. Use [Jsont.pp_value jsont ()]
248248- for pretty-printing. *)
249249-end
250250-251251-(** {1 Control Messages} *)
252252-253253-type control_request = {
254254- type_ : [ `Control_request ];
255255- request_id : string;
256256- request : Request.t;
257257- unknown : Unknown.t;
258258-}
259259-(** Control request message. *)
260260-261261-type control_response = {
262262- type_ : [ `Control_response ];
263263- response : Response.t;
264264- unknown : Unknown.t;
265265-}
266266-(** Control response message. *)
267267-268268-val control_request_jsont : control_request Jsont.t
269269-(** [control_request_jsont] is the jsont codec for control request messages. *)
270270-271271-val control_response_jsont : control_response Jsont.t
272272-(** [control_response_jsont] is the jsont codec for control response messages.
273273-*)
274274-275275-type t =
276276- | Request of control_request
277277- | Response of control_response (** The type of SDK control messages. *)
278278-279279-val create_request :
280280- request_id:string -> request:Request.t -> ?unknown:Unknown.t -> unit -> t
281281-(** [create_request ~request_id ~request ?unknown ()] creates a control request
282282- message. *)
283283-284284-val create_response : response:Response.t -> ?unknown:Unknown.t -> unit -> t
285285-(** [create_response ~response ?unknown ()] creates a control response message.
286286-*)
287287-288288-val jsont : t Jsont.t
289289-(** [jsont] is the jsont codec for control messages. Use
290290- [Jsont.pp_value jsont ()] for pretty-printing. *)
291291-292292-(** {1 Logging} *)
293293-294294-val log_request : Request.t -> unit
295295-(** [log_request req] logs an SDK control request. *)
296296-297297-val log_response : Response.t -> unit
298298-(** [log_response resp] logs an SDK control response. *)
299299-300300-(** {1 Server Information}
301301-302302- Server information provides metadata about the Claude CLI server, including
303303- version, capabilities, available commands, and output styles.
304304-305305- {2 Use Cases}
306306-307307- - Feature detection: Check if specific capabilities are available
308308- - Version compatibility: Ensure minimum version requirements
309309- - Debugging: Log server information for troubleshooting
310310- - Dynamic adaptation: Adjust SDK behavior based on capabilities
311311-312312- {2 Example}
313313-314314- {[
315315- let info = Client.get_server_info client in
316316- Printf.printf "Claude CLI version: %s\n" (Server_info.version info);
317317-318318- if List.mem "structured-output" (Server_info.capabilities info) then
319319- Printf.printf "Structured output is supported\n"
320320- else Printf.printf "Structured output not available\n"
321321- ]} *)
322322-323323-module Server_info : sig
324324- (** Server information and capabilities. *)
325325-326326- type t = {
327327- version : string; (** Server version string (e.g., "2.0.0") *)
328328- capabilities : string list;
329329- (** Available server capabilities (e.g., "hooks", "structured-output")
330330- *)
331331- commands : string list; (** Available CLI commands *)
332332- output_styles : string list;
333333- (** Supported output formats (e.g., "json", "stream-json") *)
334334- unknown : Unknown.t; (** Unknown fields for forward compatibility *)
335335- }
336336- (** Server metadata and capabilities.
337337-338338- This information is useful for feature detection and debugging. *)
339339-340340- val create :
341341- version:string ->
342342- capabilities:string list ->
343343- commands:string list ->
344344- output_styles:string list ->
345345- ?unknown:Unknown.t ->
346346- unit ->
347347- t
348348- (** [create ~version ~capabilities ~commands ~output_styles ?unknown ()]
349349- creates server info. *)
350350-351351- val version : t -> string
352352- (** [version t] returns the server version. *)
353353-354354- val capabilities : t -> string list
355355- (** [capabilities t] returns the server capabilities. *)
356356-357357- val commands : t -> string list
358358- (** [commands t] returns available commands. *)
359359-360360- val output_styles : t -> string list
361361- (** [output_styles t] returns available output styles. *)
362362-363363- val unknown : t -> Unknown.t
364364- (** [unknown t] returns the unknown fields. *)
365365-366366- val jsont : t Jsont.t
367367- (** [jsont] is the jsont codec for server info. Use [Jsont.pp_value jsont ()]
368368- for pretty-printing. *)
369369-end
lib/server_info.cmi
This is a binary file and will not be displayed.
-141
proto/content_block.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-module Text = struct
77- type t = { text : string; unknown : Unknown.t }
88-99- let create text = { text; unknown = Unknown.empty }
1010- let make text unknown = { text; unknown }
1111- let text t = t.text
1212- let unknown t = t.unknown
1313-1414- let jsont : t Jsont.t =
1515- Jsont.Object.map ~kind:"Text" make
1616- |> Jsont.Object.mem "text" Jsont.string ~enc:text
1717- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
1818- |> Jsont.Object.finish
1919-end
2020-2121-module Tool_use = struct
2222- type t = {
2323- id : string;
2424- name : string;
2525- input : Jsont.json;
2626- unknown : Unknown.t;
2727- }
2828-2929- let create ~id ~name ~input = { id; name; input; unknown = Unknown.empty }
3030- let make id name input unknown = { id; name; input; unknown }
3131- let id t = t.id
3232- let name t = t.name
3333- let input t = t.input
3434- let unknown t = t.unknown
3535-3636- let jsont : t Jsont.t =
3737- Jsont.Object.map ~kind:"Tool_use" make
3838- |> Jsont.Object.mem "id" Jsont.string ~enc:id
3939- |> Jsont.Object.mem "name" Jsont.string ~enc:name
4040- |> Jsont.Object.mem "input" Jsont.json ~enc:input
4141- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
4242- |> Jsont.Object.finish
4343-end
4444-4545-module Tool_result = struct
4646- type t = {
4747- tool_use_id : string;
4848- content : Jsont.json option;
4949- is_error : bool option;
5050- unknown : Unknown.t;
5151- }
5252-5353- let create ~tool_use_id ?content ?is_error () =
5454- { tool_use_id; content; is_error; unknown = Unknown.empty }
5555-5656- let make tool_use_id content is_error unknown =
5757- { tool_use_id; content; is_error; unknown }
5858-5959- let tool_use_id t = t.tool_use_id
6060- let content t = t.content
6161- let is_error t = t.is_error
6262- let unknown t = t.unknown
6363-6464- let jsont : t Jsont.t =
6565- Jsont.Object.map ~kind:"Tool_result" make
6666- |> Jsont.Object.mem "tool_use_id" Jsont.string ~enc:tool_use_id
6767- |> Jsont.Object.opt_mem "content" Jsont.json ~enc:content
6868- |> Jsont.Object.opt_mem "is_error" Jsont.bool ~enc:is_error
6969- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
7070- |> Jsont.Object.finish
7171-end
7272-7373-module Thinking = struct
7474- type t = { thinking : string; signature : string; unknown : Unknown.t }
7575-7676- let create ~thinking ~signature =
7777- { thinking; signature; unknown = Unknown.empty }
7878-7979- let make thinking signature unknown = { thinking; signature; unknown }
8080- let thinking t = t.thinking
8181- let signature t = t.signature
8282- let unknown t = t.unknown
8383-8484- let jsont : t Jsont.t =
8585- Jsont.Object.map ~kind:"Thinking" make
8686- |> Jsont.Object.mem "thinking" Jsont.string ~enc:thinking
8787- |> Jsont.Object.mem "signature" Jsont.string ~enc:signature
8888- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
8989- |> Jsont.Object.finish
9090-end
9191-9292-type t =
9393- | Text of Text.t
9494- | Tool_use of Tool_use.t
9595- | Tool_result of Tool_result.t
9696- | Thinking of Thinking.t
9797-9898-let text s = Text (Text.create s)
9999-let tool_use ~id ~name ~input = Tool_use (Tool_use.create ~id ~name ~input)
100100-101101-let tool_result ~tool_use_id ?content ?is_error () =
102102- Tool_result (Tool_result.create ~tool_use_id ?content ?is_error ())
103103-104104-let thinking ~thinking ~signature =
105105- Thinking (Thinking.create ~thinking ~signature)
106106-107107-let jsont : t Jsont.t =
108108- let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
109109-110110- let case_text = case_map "text" Text.jsont (fun v -> Text v) in
111111- let case_tool_use =
112112- case_map "tool_use" Tool_use.jsont (fun v -> Tool_use v)
113113- in
114114- let case_tool_result =
115115- case_map "tool_result" Tool_result.jsont (fun v -> Tool_result v)
116116- in
117117- let case_thinking =
118118- case_map "thinking" Thinking.jsont (fun v -> Thinking v)
119119- in
120120-121121- let enc_case = function
122122- | Text v -> Jsont.Object.Case.value case_text v
123123- | Tool_use v -> Jsont.Object.Case.value case_tool_use v
124124- | Tool_result v -> Jsont.Object.Case.value case_tool_result v
125125- | Thinking v -> Jsont.Object.Case.value case_thinking v
126126- in
127127-128128- let cases =
129129- Jsont.Object.Case.
130130- [
131131- make case_text;
132132- make case_tool_use;
133133- make case_tool_result;
134134- make case_thinking;
135135- ]
136136- in
137137-138138- Jsont.Object.map ~kind:"Content_block" Fun.id
139139- |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
140140- ~tag_to_string:Fun.id ~tag_compare:String.compare
141141- |> Jsont.Object.finish
-131
proto/content_block.ml.bak
···11-module Text = struct
22- type t = { text : string; unknown : Unknown.t }
33-44- let create text = { text; unknown = Unknown.empty }
55- let make text unknown = { text; unknown }
66- let text t = t.text
77- let unknown t = t.unknown
88-99- let jsont : t Jsont.t =
1010- Jsont.Object.map ~kind:"Text" make
1111- |> Jsont.Object.mem "text" Jsont.string ~enc:text
1212- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
1313- |> Jsont.Object.finish
1414-end
1515-1616-module Tool_use = struct
1717- type t = { id : string; name : string; input : Jsont.json; unknown : Unknown.t }
1818-1919- let create ~id ~name ~input = { id; name; input; unknown = Unknown.empty }
2020- let make id name input unknown = { id; name; input; unknown }
2121- let id t = t.id
2222- let name t = t.name
2323- let input t = t.input
2424- let unknown t = t.unknown
2525-2626- let jsont : t Jsont.t =
2727- Jsont.Object.map ~kind:"Tool_use" make
2828- |> Jsont.Object.mem "id" Jsont.string ~enc:id
2929- |> Jsont.Object.mem "name" Jsont.string ~enc:name
3030- |> Jsont.Object.mem "input" Jsont.json ~enc:input
3131- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
3232- |> Jsont.Object.finish
3333-end
3434-3535-module Tool_result = struct
3636- type t = {
3737- tool_use_id : string;
3838- content : string option;
3939- is_error : bool option;
4040- unknown : Unknown.t;
4141- }
4242-4343- let create ~tool_use_id ?content ?is_error () =
4444- { tool_use_id; content; is_error; unknown = Unknown.empty }
4545-4646- let make tool_use_id content is_error unknown =
4747- { tool_use_id; content; is_error; unknown }
4848-4949- let tool_use_id t = t.tool_use_id
5050- let content t = t.content
5151- let is_error t = t.is_error
5252- let unknown t = t.unknown
5353-5454- let jsont : t Jsont.t =
5555- Jsont.Object.map ~kind:"Tool_result" make
5656- |> Jsont.Object.mem "tool_use_id" Jsont.string ~enc:tool_use_id
5757- |> Jsont.Object.opt_mem "content" Jsont.string ~enc:content
5858- |> Jsont.Object.opt_mem "is_error" Jsont.bool ~enc:is_error
5959- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
6060- |> Jsont.Object.finish
6161-end
6262-6363-module Thinking = struct
6464- type t = { thinking : string; signature : string; unknown : Unknown.t }
6565-6666- let create ~thinking ~signature =
6767- { thinking; signature; unknown = Unknown.empty }
6868-6969- let make thinking signature unknown = { thinking; signature; unknown }
7070- let thinking t = t.thinking
7171- let signature t = t.signature
7272- let unknown t = t.unknown
7373-7474- let jsont : t Jsont.t =
7575- Jsont.Object.map ~kind:"Thinking" make
7676- |> Jsont.Object.mem "thinking" Jsont.string ~enc:thinking
7777- |> Jsont.Object.mem "signature" Jsont.string ~enc:signature
7878- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
7979- |> Jsont.Object.finish
8080-end
8181-8282-type t =
8383- | Text of Text.t
8484- | Tool_use of Tool_use.t
8585- | Tool_result of Tool_result.t
8686- | Thinking of Thinking.t
8787-8888-let text s = Text (Text.create s)
8989-let tool_use ~id ~name ~input = Tool_use (Tool_use.create ~id ~name ~input)
9090-9191-let tool_result ~tool_use_id ?content ?is_error () =
9292- Tool_result (Tool_result.create ~tool_use_id ?content ?is_error ())
9393-9494-let thinking ~thinking ~signature =
9595- Thinking (Thinking.create ~thinking ~signature)
9696-9797-let jsont : t Jsont.t =
9898- let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
9999-100100- let case_text = case_map "text" Text.jsont (fun v -> Text v) in
101101- let case_tool_use =
102102- case_map "tool_use" Tool_use.jsont (fun v -> Tool_use v)
103103- in
104104- let case_tool_result =
105105- case_map "tool_result" Tool_result.jsont (fun v -> Tool_result v)
106106- in
107107- let case_thinking =
108108- case_map "thinking" Thinking.jsont (fun v -> Thinking v)
109109- in
110110-111111- let enc_case = function
112112- | Text v -> Jsont.Object.Case.value case_text v
113113- | Tool_use v -> Jsont.Object.Case.value case_tool_use v
114114- | Tool_result v -> Jsont.Object.Case.value case_tool_result v
115115- | Thinking v -> Jsont.Object.Case.value case_thinking v
116116- in
117117-118118- let cases =
119119- Jsont.Object.Case.
120120- [
121121- make case_text;
122122- make case_tool_use;
123123- make case_tool_result;
124124- make case_thinking;
125125- ]
126126- in
127127-128128- Jsont.Object.map ~kind:"Content_block" Fun.id
129129- |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
130130- ~tag_to_string:Fun.id ~tag_compare:String.compare
131131- |> Jsont.Object.finish
-157
proto/content_block.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Content blocks for Claude messages wire format.
77-88- This module defines the wire format types for content blocks that can appear
99- in Claude messages, including text, tool use, tool results, and thinking
1010- blocks. *)
1111-1212-(** {1 Text Blocks} *)
1313-1414-module Text : sig
1515- (** Plain text content blocks. *)
1616-1717- type t
1818- (** The type of text blocks. *)
1919-2020- val jsont : t Jsont.t
2121- (** [jsont] is the Jsont codec for text blocks. Use [Jsont.Json.encode jsont]
2222- and [Jsont.Json.decode jsont] for serialization. Use
2323- [Jsont.pp_value jsont ()] for pretty-printing. *)
2424-2525- val create : string -> t
2626- (** [create text] creates a new text block with the given text content. *)
2727-2828- val text : t -> string
2929- (** [text t] returns the text content of the block. *)
3030-3131- val unknown : t -> Unknown.t
3232- (** [unknown t] returns any unknown fields from JSON parsing. *)
3333-end
3434-3535-(** {1 Tool Use Blocks} *)
3636-3737-module Tool_use : sig
3838- (** Tool invocation requests from the assistant. *)
3939-4040- type t
4141- (** The type of tool use blocks. *)
4242-4343- val jsont : t Jsont.t
4444- (** [jsont] is the Jsont codec for tool use blocks. Use
4545- [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
4646- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
4747-4848- val create : id:string -> name:string -> input:Jsont.json -> t
4949- (** [create ~id ~name ~input] creates a new tool use block.
5050- @param id Unique identifier for this tool invocation
5151- @param name Name of the tool to invoke
5252- @param input Parameters for the tool as raw JSON *)
5353-5454- val id : t -> string
5555- (** [id t] returns the unique identifier of the tool use. *)
5656-5757- val name : t -> string
5858- (** [name t] returns the name of the tool being invoked. *)
5959-6060- val input : t -> Jsont.json
6161- (** [input t] returns the input parameters for the tool as raw JSON. *)
6262-6363- val unknown : t -> Unknown.t
6464- (** [unknown t] returns any unknown fields from JSON parsing. *)
6565-end
6666-6767-(** {1 Tool Result Blocks} *)
6868-6969-module Tool_result : sig
7070- (** Results from tool invocations. *)
7171-7272- type t
7373- (** The type of tool result blocks. *)
7474-7575- val jsont : t Jsont.t
7676- (** [jsont] is the Jsont codec for tool result blocks. Use
7777- [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
7878- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
7979-8080- val create :
8181- tool_use_id:string -> ?content:Jsont.json -> ?is_error:bool -> unit -> t
8282- (** [create ~tool_use_id ?content ?is_error ()] creates a new tool result
8383- block.
8484- @param tool_use_id The ID of the corresponding tool use block
8585- @param content
8686- Optional result content (can be string or array of content blocks)
8787- @param is_error Whether the tool execution resulted in an error *)
8888-8989- val tool_use_id : t -> string
9090- (** [tool_use_id t] returns the ID of the corresponding tool use. *)
9191-9292- val content : t -> Jsont.json option
9393- (** [content t] returns the optional result content as raw JSON. *)
9494-9595- val is_error : t -> bool option
9696- (** [is_error t] returns whether this result represents an error. *)
9797-9898- val unknown : t -> Unknown.t
9999- (** [unknown t] returns any unknown fields from JSON parsing. *)
100100-end
101101-102102-(** {1 Thinking Blocks} *)
103103-104104-module Thinking : sig
105105- (** Assistant's internal reasoning blocks. *)
106106-107107- type t
108108- (** The type of thinking blocks. *)
109109-110110- val jsont : t Jsont.t
111111- (** [jsont] is the Jsont codec for thinking blocks. Use
112112- [Jsont.Json.encode jsont] and [Jsont.Json.decode jsont] for serialization.
113113- Use [Jsont.pp_value jsont ()] for pretty-printing. *)
114114-115115- val create : thinking:string -> signature:string -> t
116116- (** [create ~thinking ~signature] creates a new thinking block.
117117- @param thinking The assistant's internal reasoning
118118- @param signature Cryptographic signature for verification *)
119119-120120- val thinking : t -> string
121121- (** [thinking t] returns the thinking content. *)
122122-123123- val signature : t -> string
124124- (** [signature t] returns the cryptographic signature. *)
125125-126126- val unknown : t -> Unknown.t
127127- (** [unknown t] returns any unknown fields from JSON parsing. *)
128128-end
129129-130130-(** {1 Content Block Union Type} *)
131131-132132-type t =
133133- | Text of Text.t
134134- | Tool_use of Tool_use.t
135135- | Tool_result of Tool_result.t
136136- | Thinking of Thinking.t
137137- (** The type of content blocks, which can be text, tool use, tool result,
138138- or thinking. *)
139139-140140-val jsont : t Jsont.t
141141-(** [jsont] is the Jsont codec for content blocks. Use [Jsont.Json.encode jsont]
142142- and [Jsont.Json.decode jsont] for serialization. Use
143143- [Jsont.pp_value jsont ()] for pretty-printing. *)
144144-145145-val text : string -> t
146146-(** [text s] creates a text content block. *)
147147-148148-val tool_use : id:string -> name:string -> input:Jsont.json -> t
149149-(** [tool_use ~id ~name ~input] creates a tool use content block. *)
150150-151151-val tool_result :
152152- tool_use_id:string -> ?content:Jsont.json -> ?is_error:bool -> unit -> t
153153-(** [tool_result ~tool_use_id ?content ?is_error ()] creates a tool result
154154- content block. Content can be a string or an array of content blocks. *)
155155-156156-val thinking : thinking:string -> signature:string -> t
157157-(** [thinking ~thinking ~signature] creates a thinking content block. *)
-416
proto/control.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Control protocol wire format for SDK communication. *)
77-88-module Request = struct
99- (* Individual record types for each request variant - private to this module *)
1010- type permission_r = {
1111- tool_name : string;
1212- input : Jsont.json;
1313- permission_suggestions : Permissions.Update.t list option;
1414- blocked_path : string option;
1515- unknown : Unknown.t;
1616- }
1717-1818- type initialize_r = {
1919- hooks : (string * Jsont.json) list option;
2020- unknown : Unknown.t;
2121- }
2222-2323- type set_permission_mode_r = {
2424- mode : Permissions.Mode.t;
2525- unknown : Unknown.t;
2626- }
2727-2828- type hook_callback_r = {
2929- callback_id : string;
3030- input : Jsont.json;
3131- tool_use_id : string option;
3232- unknown : Unknown.t;
3333- }
3434-3535- type mcp_message_r = {
3636- server_name : string;
3737- message : Jsont.json;
3838- unknown : Unknown.t;
3939- }
4040-4141- type set_model_r = { model : string; unknown : Unknown.t }
4242-4343- type t =
4444- | Interrupt
4545- | Permission of permission_r
4646- | Initialize of initialize_r
4747- | Set_permission_mode of set_permission_mode_r
4848- | Hook_callback of hook_callback_r
4949- | Mcp_message of mcp_message_r
5050- | Set_model of set_model_r
5151- | Get_server_info
5252-5353- let interrupt () = Interrupt
5454-5555- let permission ~tool_name ~input ?permission_suggestions ?blocked_path () =
5656- Permission
5757- {
5858- tool_name;
5959- input;
6060- permission_suggestions;
6161- blocked_path;
6262- unknown = Unknown.empty;
6363- }
6464-6565- let initialize ?hooks () = Initialize { hooks; unknown = Unknown.empty }
6666-6767- let set_permission_mode ~mode () =
6868- Set_permission_mode { mode; unknown = Unknown.empty }
6969-7070- let hook_callback ~callback_id ~input ?tool_use_id () =
7171- Hook_callback { callback_id; input; tool_use_id; unknown = Unknown.empty }
7272-7373- let mcp_message ~server_name ~message () =
7474- Mcp_message { server_name; message; unknown = Unknown.empty }
7575-7676- let set_model ~model () = Set_model { model; unknown = Unknown.empty }
7777- let get_server_info () = Get_server_info
7878-7979- (* Individual record codecs *)
8080- let interrupt_jsont : unit Jsont.t =
8181- Jsont.Object.map ~kind:"Interrupt" (fun _unknown -> ())
8282- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun () -> Unknown.empty)
8383- |> Jsont.Object.finish
8484-8585- let permission_jsont : permission_r Jsont.t =
8686- let make tool_name input permission_suggestions blocked_path unknown :
8787- permission_r =
8888- { tool_name; input; permission_suggestions; blocked_path; unknown }
8989- in
9090- Jsont.Object.map ~kind:"Permission" make
9191- |> Jsont.Object.mem "toolName" Jsont.string ~enc:(fun (r : permission_r) ->
9292- r.tool_name)
9393- |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : permission_r) ->
9494- r.input)
9595- |> Jsont.Object.opt_mem "permissionSuggestions"
9696- (Jsont.list Permissions.Update.jsont) ~enc:(fun (r : permission_r) ->
9797- r.permission_suggestions)
9898- |> Jsont.Object.opt_mem "blockedPath" Jsont.string
9999- ~enc:(fun (r : permission_r) -> r.blocked_path)
100100- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : permission_r) ->
101101- r.unknown)
102102- |> Jsont.Object.finish
103103-104104- let initialize_jsont : initialize_r Jsont.t =
105105- (* The hooks field is an object with string keys and json values *)
106106- let hooks_map_jsont = Jsont.Object.as_string_map Jsont.json in
107107- let module StringMap = Map.Make (String) in
108108- let hooks_jsont =
109109- Jsont.map
110110- ~dec:(fun m -> StringMap.bindings m)
111111- ~enc:(fun l -> StringMap.of_seq (List.to_seq l))
112112- hooks_map_jsont
113113- in
114114- let make hooks unknown = { hooks; unknown } in
115115- Jsont.Object.map ~kind:"Initialize" make
116116- |> Jsont.Object.opt_mem "hooks" hooks_jsont ~enc:(fun (r : initialize_r) ->
117117- r.hooks)
118118- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : initialize_r) ->
119119- r.unknown)
120120- |> Jsont.Object.finish
121121-122122- let set_permission_mode_jsont : set_permission_mode_r Jsont.t =
123123- let make mode unknown = { mode; unknown } in
124124- Jsont.Object.map ~kind:"SetPermissionMode" make
125125- |> Jsont.Object.mem "mode" Permissions.Mode.jsont
126126- ~enc:(fun (r : set_permission_mode_r) -> r.mode)
127127- |> Jsont.Object.keep_unknown Unknown.mems
128128- ~enc:(fun (r : set_permission_mode_r) -> r.unknown)
129129- |> Jsont.Object.finish
130130-131131- let hook_callback_jsont : hook_callback_r Jsont.t =
132132- let make callback_id input tool_use_id unknown =
133133- { callback_id; input; tool_use_id; unknown }
134134- in
135135- Jsont.Object.map ~kind:"HookCallback" make
136136- |> Jsont.Object.mem "callbackId" Jsont.string
137137- ~enc:(fun (r : hook_callback_r) -> r.callback_id)
138138- |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : hook_callback_r) ->
139139- r.input)
140140- |> Jsont.Object.opt_mem "toolUseId" Jsont.string
141141- ~enc:(fun (r : hook_callback_r) -> r.tool_use_id)
142142- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : hook_callback_r) ->
143143- r.unknown)
144144- |> Jsont.Object.finish
145145-146146- let mcp_message_jsont : mcp_message_r Jsont.t =
147147- let make server_name message unknown = { server_name; message; unknown } in
148148- Jsont.Object.map ~kind:"McpMessage" make
149149- |> Jsont.Object.mem "serverName" Jsont.string
150150- ~enc:(fun (r : mcp_message_r) -> r.server_name)
151151- |> Jsont.Object.mem "message" Jsont.json ~enc:(fun (r : mcp_message_r) ->
152152- r.message)
153153- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : mcp_message_r) ->
154154- r.unknown)
155155- |> Jsont.Object.finish
156156-157157- let set_model_jsont : set_model_r Jsont.t =
158158- let make model unknown = { model; unknown } in
159159- Jsont.Object.map ~kind:"SetModel" make
160160- |> Jsont.Object.mem "model" Jsont.string ~enc:(fun (r : set_model_r) ->
161161- r.model)
162162- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : set_model_r) ->
163163- r.unknown)
164164- |> Jsont.Object.finish
165165-166166- let get_server_info_jsont : unit Jsont.t =
167167- Jsont.Object.map ~kind:"GetServerInfo" (fun _unknown -> ())
168168- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun () -> Unknown.empty)
169169- |> Jsont.Object.finish
170170-171171- (* Main variant codec using subtype discriminator *)
172172- let jsont : t Jsont.t =
173173- let case_interrupt =
174174- Jsont.Object.Case.map "interrupt" interrupt_jsont ~dec:(fun () ->
175175- Interrupt)
176176- in
177177- let case_permission =
178178- Jsont.Object.Case.map "canUseTool" permission_jsont ~dec:(fun v ->
179179- Permission v)
180180- in
181181- let case_initialize =
182182- Jsont.Object.Case.map "initialize" initialize_jsont ~dec:(fun v ->
183183- Initialize v)
184184- in
185185- let case_set_permission_mode =
186186- Jsont.Object.Case.map "setPermissionMode" set_permission_mode_jsont
187187- ~dec:(fun v -> Set_permission_mode v)
188188- in
189189- let case_hook_callback =
190190- Jsont.Object.Case.map "hookCallback" hook_callback_jsont ~dec:(fun v ->
191191- Hook_callback v)
192192- in
193193- let case_mcp_message =
194194- Jsont.Object.Case.map "mcpMessage" mcp_message_jsont ~dec:(fun v ->
195195- Mcp_message v)
196196- in
197197- let case_set_model =
198198- Jsont.Object.Case.map "setModel" set_model_jsont ~dec:(fun v ->
199199- Set_model v)
200200- in
201201- let case_get_server_info =
202202- Jsont.Object.Case.map "getServerInfo" get_server_info_jsont
203203- ~dec:(fun () -> Get_server_info)
204204- in
205205-206206- let enc_case = function
207207- | Interrupt -> Jsont.Object.Case.value case_interrupt ()
208208- | Permission v -> Jsont.Object.Case.value case_permission v
209209- | Initialize v -> Jsont.Object.Case.value case_initialize v
210210- | Set_permission_mode v ->
211211- Jsont.Object.Case.value case_set_permission_mode v
212212- | Hook_callback v -> Jsont.Object.Case.value case_hook_callback v
213213- | Mcp_message v -> Jsont.Object.Case.value case_mcp_message v
214214- | Set_model v -> Jsont.Object.Case.value case_set_model v
215215- | Get_server_info -> Jsont.Object.Case.value case_get_server_info ()
216216- in
217217-218218- let cases =
219219- Jsont.Object.Case.
220220- [
221221- make case_interrupt;
222222- make case_permission;
223223- make case_initialize;
224224- make case_set_permission_mode;
225225- make case_hook_callback;
226226- make case_mcp_message;
227227- make case_set_model;
228228- make case_get_server_info;
229229- ]
230230- in
231231-232232- Jsont.Object.map ~kind:"Request" Fun.id
233233- |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
234234- ~tag_to_string:Fun.id ~tag_compare:String.compare
235235- |> Jsont.Object.finish
236236-end
237237-238238-module Response = struct
239239- (* Standard JSON-RPC 2.0 error codes using polymorphic variants *)
240240- module Error_code = struct
241241- type t =
242242- [ `Parse_error
243243- | `Invalid_request
244244- | `Method_not_found
245245- | `Invalid_params
246246- | `Internal_error
247247- | `Custom of int ]
248248-249249- let to_int : [< t ] -> int = function
250250- | `Parse_error -> -32700
251251- | `Invalid_request -> -32600
252252- | `Method_not_found -> -32601
253253- | `Invalid_params -> -32602
254254- | `Internal_error -> -32603
255255- | `Custom n -> n
256256-257257- let of_int = function
258258- | -32700 -> `Parse_error
259259- | -32600 -> `Invalid_request
260260- | -32601 -> `Method_not_found
261261- | -32602 -> `Invalid_params
262262- | -32603 -> `Internal_error
263263- | n -> `Custom n
264264- end
265265-266266- (* Structured error similar to JSON-RPC *)
267267- type error_detail = { code : int; message : string; data : Jsont.json option }
268268-269269- let error_detail ~code ~message ?data () =
270270- { code = Error_code.to_int code; message; data }
271271-272272- let error_detail_jsont : error_detail Jsont.t =
273273- let make code message data = { code; message; data } in
274274- Jsont.Object.map ~kind:"ErrorDetail" make
275275- |> Jsont.Object.mem "code" Jsont.int ~enc:(fun e -> e.code)
276276- |> Jsont.Object.mem "message" Jsont.string ~enc:(fun e -> e.message)
277277- |> Jsont.Object.opt_mem "data" Jsont.json ~enc:(fun e -> e.data)
278278- |> Jsont.Object.finish
279279-280280- (* Individual record types for each response variant *)
281281- type success_r = {
282282- request_id : string;
283283- response : Jsont.json option;
284284- unknown : Unknown.t;
285285- }
286286-287287- type error_r = {
288288- request_id : string;
289289- error : error_detail;
290290- unknown : Unknown.t;
291291- }
292292-293293- type t = Success of success_r | Error of error_r
294294-295295- let success ~request_id ?response () =
296296- Success { request_id; response; unknown = Unknown.empty }
297297-298298- let error ~request_id ~error () =
299299- Error { request_id; error; unknown = Unknown.empty }
300300-301301- (* Individual record codecs *)
302302- let success_jsont : success_r Jsont.t =
303303- let make request_id response unknown = { request_id; response; unknown } in
304304- Jsont.Object.map ~kind:"Success" make
305305- |> Jsont.Object.mem "requestId" Jsont.string ~enc:(fun (r : success_r) ->
306306- r.request_id)
307307- |> Jsont.Object.opt_mem "response" Jsont.json ~enc:(fun (r : success_r) ->
308308- r.response)
309309- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : success_r) ->
310310- r.unknown)
311311- |> Jsont.Object.finish
312312-313313- let error_jsont : error_r Jsont.t =
314314- let make request_id error unknown = { request_id; error; unknown } in
315315- Jsont.Object.map ~kind:"Error" make
316316- |> Jsont.Object.mem "requestId" Jsont.string ~enc:(fun (r : error_r) ->
317317- r.request_id)
318318- |> Jsont.Object.mem "error" error_detail_jsont ~enc:(fun (r : error_r) ->
319319- r.error)
320320- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : error_r) ->
321321- r.unknown)
322322- |> Jsont.Object.finish
323323-324324- (* Main variant codec using subtype discriminator *)
325325- let jsont : t Jsont.t =
326326- let case_success =
327327- Jsont.Object.Case.map "success" success_jsont ~dec:(fun v -> Success v)
328328- in
329329- let case_error =
330330- Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v)
331331- in
332332-333333- let enc_case = function
334334- | Success v -> Jsont.Object.Case.value case_success v
335335- | Error v -> Jsont.Object.Case.value case_error v
336336- in
337337-338338- let cases = Jsont.Object.Case.[ make case_success; make case_error ] in
339339-340340- Jsont.Object.map ~kind:"Response" Fun.id
341341- |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
342342- ~tag_to_string:Fun.id ~tag_compare:String.compare
343343- |> Jsont.Object.finish
344344-end
345345-346346-type request_envelope = {
347347- request_id : string;
348348- request : Request.t;
349349- unknown : Unknown.t;
350350-}
351351-352352-type response_envelope = { response : Response.t; unknown : Unknown.t }
353353-354354-let create_request ~request_id ~request () =
355355- { request_id; request; unknown = Unknown.empty }
356356-357357-let create_response ~response () = { response; unknown = Unknown.empty }
358358-359359-(* Envelope codecs *)
360360-let request_envelope_jsont : request_envelope Jsont.t =
361361- let make request_id request unknown = { request_id; request; unknown } in
362362- Jsont.Object.map ~kind:"RequestEnvelope" make
363363- |> Jsont.Object.mem "requestId" Jsont.string
364364- ~enc:(fun (r : request_envelope) -> r.request_id)
365365- |> Jsont.Object.mem "request" Request.jsont
366366- ~enc:(fun (r : request_envelope) -> r.request)
367367- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : request_envelope) ->
368368- r.unknown)
369369- |> Jsont.Object.finish
370370-371371-let response_envelope_jsont : response_envelope Jsont.t =
372372- let make response unknown = { response; unknown } in
373373- Jsont.Object.map ~kind:"ResponseEnvelope" make
374374- |> Jsont.Object.mem "response" Response.jsont
375375- ~enc:(fun (r : response_envelope) -> r.response)
376376- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : response_envelope) ->
377377- r.unknown)
378378- |> Jsont.Object.finish
379379-380380-(** Server information *)
381381-module Server_info = struct
382382- type t = {
383383- version : string;
384384- capabilities : string list;
385385- commands : string list;
386386- output_styles : string list;
387387- unknown : Unknown.t;
388388- }
389389-390390- let create ~version ~capabilities ~commands ~output_styles () =
391391- { version; capabilities; commands; output_styles; unknown = Unknown.empty }
392392-393393- let version t = t.version
394394- let capabilities t = t.capabilities
395395- let commands t = t.commands
396396- let output_styles t = t.output_styles
397397- let unknown t = t.unknown
398398-399399- let jsont : t Jsont.t =
400400- let make version capabilities commands output_styles unknown =
401401- { version; capabilities; commands; output_styles; unknown }
402402- in
403403- Jsont.Object.map ~kind:"ServerInfo" make
404404- |> Jsont.Object.mem "version" Jsont.string ~enc:(fun r -> r.version)
405405- |> Jsont.Object.mem "capabilities" (Jsont.list Jsont.string)
406406- ~enc:(fun r -> r.capabilities)
407407- ~dec_absent:[]
408408- |> Jsont.Object.mem "commands" (Jsont.list Jsont.string)
409409- ~enc:(fun r -> r.commands)
410410- ~dec_absent:[]
411411- |> Jsont.Object.mem "outputStyles" (Jsont.list Jsont.string)
412412- ~enc:(fun r -> r.output_styles)
413413- ~dec_absent:[]
414414- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun r -> r.unknown)
415415- |> Jsont.Object.finish
416416-end
-250
proto/control.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Control protocol wire format for SDK communication.
77-88- This module defines the wire format for the SDK control protocol used for
99- bidirectional communication between the SDK and the Claude CLI. It handles
1010- JSON serialization and deserialization of control messages.
1111-1212- The control protocol enables:
1313- - Permission requests for tool usage authorization
1414- - Hook callbacks for intercepting and modifying tool execution
1515- - Dynamic control for changing settings mid-conversation
1616- - Server introspection for querying capabilities *)
1717-1818-(** {1 Request Types} *)
1919-2020-module Request : sig
2121- (** SDK control request types. *)
2222-2323- type permission_r = private {
2424- tool_name : string;
2525- input : Jsont.json;
2626- permission_suggestions : Permissions.Update.t list option;
2727- blocked_path : string option;
2828- unknown : Unknown.t;
2929- }
3030-3131- type initialize_r = private {
3232- hooks : (string * Jsont.json) list option;
3333- unknown : Unknown.t;
3434- }
3535-3636- type set_permission_mode_r = private {
3737- mode : Permissions.Mode.t;
3838- unknown : Unknown.t;
3939- }
4040-4141- type hook_callback_r = private {
4242- callback_id : string;
4343- input : Jsont.json;
4444- tool_use_id : string option;
4545- unknown : Unknown.t;
4646- }
4747-4848- type mcp_message_r = private {
4949- server_name : string;
5050- message : Jsont.json;
5151- unknown : Unknown.t;
5252- }
5353-5454- type set_model_r = private { model : string; unknown : Unknown.t }
5555-5656- type t =
5757- | Interrupt
5858- | Permission of permission_r
5959- | Initialize of initialize_r
6060- | Set_permission_mode of set_permission_mode_r
6161- | Hook_callback of hook_callback_r
6262- | Mcp_message of mcp_message_r
6363- | Set_model of set_model_r
6464- | Get_server_info
6565- (** The type of SDK control requests. Wire format uses "subtype" field:
6666- "interrupt", "canUseTool", "initialize", "setPermissionMode",
6767- "hookCallback", "mcpMessage", "setModel", "getServerInfo". *)
6868-6969- val jsont : t Jsont.t
7070- (** [jsont] is the Jsont codec for requests. *)
7171-7272- val interrupt : unit -> t
7373- (** [interrupt ()] creates an interrupt request. *)
7474-7575- val permission :
7676- tool_name:string ->
7777- input:Jsont.json ->
7878- ?permission_suggestions:Permissions.Update.t list ->
7979- ?blocked_path:string ->
8080- unit ->
8181- t
8282- (** [permission ~tool_name ~input ?permission_suggestions ?blocked_path ()]
8383- creates a permission request. *)
8484-8585- val initialize : ?hooks:(string * Jsont.json) list -> unit -> t
8686- (** [initialize ?hooks ()] creates an initialize request. *)
8787-8888- val set_permission_mode : mode:Permissions.Mode.t -> unit -> t
8989- (** [set_permission_mode ~mode ()] creates a permission mode change request.
9090- *)
9191-9292- val hook_callback :
9393- callback_id:string -> input:Jsont.json -> ?tool_use_id:string -> unit -> t
9494- (** [hook_callback ~callback_id ~input ?tool_use_id ()] creates a hook
9595- callback request. *)
9696-9797- val mcp_message : server_name:string -> message:Jsont.json -> unit -> t
9898- (** [mcp_message ~server_name ~message ()] creates an MCP message request. *)
9999-100100- val set_model : model:string -> unit -> t
101101- (** [set_model ~model ()] creates a model change request. *)
102102-103103- val get_server_info : unit -> t
104104- (** [get_server_info ()] creates a server info request. *)
105105-end
106106-107107-(** {1 Response Types} *)
108108-109109-module Response : sig
110110- (** SDK control response types. *)
111111-112112- (** Standard JSON-RPC 2.0 error codes.
113113-114114- These codes follow the JSON-RPC 2.0 specification for structured error
115115- responses. Using the typed codes instead of raw integers improves code
116116- clarity and prevents typos. Polymorphic variants allow for easy extension.
117117- *)
118118- module Error_code : sig
119119- type t =
120120- [ `Parse_error (** -32700: Invalid JSON received *)
121121- | `Invalid_request (** -32600: The request object is invalid *)
122122- | `Method_not_found (** -32601: The requested method does not exist *)
123123- | `Invalid_params (** -32602: Invalid method parameters *)
124124- | `Internal_error (** -32603: Internal server error *)
125125- | `Custom of int (** Application-specific error codes *) ]
126126-127127- val to_int : [< t ] -> int
128128- (** [to_int t] converts an error code to its integer representation. *)
129129-130130- val of_int : int -> t
131131- (** [of_int n] converts an integer to an error code. Standard codes are
132132- mapped to their variants, others become [`Custom n]. *)
133133- end
134134-135135- type error_detail = {
136136- code : int; (** Error code for programmatic handling *)
137137- message : string; (** Human-readable error message *)
138138- data : Jsont.json option; (** Optional additional error data *)
139139- }
140140- (** Structured error detail similar to JSON-RPC. *)
141141-142142- val error_detail :
143143- code:[< Error_code.t ] ->
144144- message:string ->
145145- ?data:Jsont.json ->
146146- unit ->
147147- error_detail
148148- (** [error_detail ~code ~message ?data ()] creates a structured error detail
149149- using typed error codes.
150150-151151- Example:
152152- {[
153153- error_detail ~code:`Method_not_found ~message:"Hook callback not found"
154154- ()
155155- ]} *)
156156-157157- val error_detail_jsont : error_detail Jsont.t
158158- (** [error_detail_jsont] is the Jsont codec for error details. *)
159159-160160- type success_r = private {
161161- request_id : string;
162162- response : Jsont.json option;
163163- unknown : Unknown.t;
164164- }
165165-166166- type error_r = private {
167167- request_id : string;
168168- error : error_detail;
169169- unknown : Unknown.t;
170170- }
171171-172172- type t =
173173- | Success of success_r
174174- | Error of error_r
175175- (** The type of SDK control responses. Wire format uses "subtype" field:
176176- "success", "error". *)
177177-178178- val jsont : t Jsont.t
179179- (** [jsont] is the Jsont codec for responses. *)
180180-181181- val success : request_id:string -> ?response:Jsont.json -> unit -> t
182182- (** [success ~request_id ?response ()] creates a success response. *)
183183-184184- val error : request_id:string -> error:error_detail -> unit -> t
185185- (** [error ~request_id ~error ()] creates an error response with structured
186186- error detail. *)
187187-end
188188-189189-(** {1 Control Envelopes} *)
190190-191191-type request_envelope = {
192192- request_id : string;
193193- request : Request.t;
194194- unknown : Unknown.t;
195195-}
196196-(** Control request envelope. Wire format has "type": "control_request". *)
197197-198198-type response_envelope = { response : Response.t; unknown : Unknown.t }
199199-(** Control response envelope. Wire format has "type": "control_response". *)
200200-201201-val request_envelope_jsont : request_envelope Jsont.t
202202-(** [request_envelope_jsont] is the Jsont codec for request envelopes. *)
203203-204204-val response_envelope_jsont : response_envelope Jsont.t
205205-(** [response_envelope_jsont] is the Jsont codec for response envelopes. *)
206206-207207-val create_request :
208208- request_id:string -> request:Request.t -> unit -> request_envelope
209209-(** [create_request ~request_id ~request ()] creates a control request envelope.
210210-*)
211211-212212-val create_response : response:Response.t -> unit -> response_envelope
213213-(** [create_response ~response ()] creates a control response envelope. *)
214214-215215-(** {1 Server Information} *)
216216-217217-module Server_info : sig
218218- (** Server information and capabilities. *)
219219-220220- type t
221221- (** Server metadata and capabilities. *)
222222-223223- val jsont : t Jsont.t
224224- (** [jsont] is the Jsont codec for server info. *)
225225-226226- val create :
227227- version:string ->
228228- capabilities:string list ->
229229- commands:string list ->
230230- output_styles:string list ->
231231- unit ->
232232- t
233233- (** [create ~version ~capabilities ~commands ~output_styles ()] creates server
234234- info. *)
235235-236236- val version : t -> string
237237- (** [version t] returns the server version. *)
238238-239239- val capabilities : t -> string list
240240- (** [capabilities t] returns the server capabilities. *)
241241-242242- val commands : t -> string list
243243- (** [commands t] returns available commands. *)
244244-245245- val output_styles : t -> string list
246246- (** [output_styles t] returns available output styles. *)
247247-248248- val unknown : t -> Unknown.t
249249- (** [unknown t] returns the unknown fields. *)
250250-end
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Claude Code Hooks System - Wire Format
77-88- This module defines the wire format for hook configuration. *)
99-1010-(** Hook events that can be intercepted *)
1111-type event =
1212- | Pre_tool_use
1313- | Post_tool_use
1414- | User_prompt_submit
1515- | Stop
1616- | Subagent_stop
1717- | Pre_compact
1818-1919-let event_to_string = function
2020- | Pre_tool_use -> "PreToolUse"
2121- | Post_tool_use -> "PostToolUse"
2222- | User_prompt_submit -> "UserPromptSubmit"
2323- | Stop -> "Stop"
2424- | Subagent_stop -> "SubagentStop"
2525- | Pre_compact -> "PreCompact"
2626-2727-let event_of_string = function
2828- | "PreToolUse" -> Pre_tool_use
2929- | "PostToolUse" -> Post_tool_use
3030- | "UserPromptSubmit" -> User_prompt_submit
3131- | "Stop" -> Stop
3232- | "SubagentStop" -> Subagent_stop
3333- | "PreCompact" -> Pre_compact
3434- | s -> raise (Invalid_argument (Printf.sprintf "Unknown hook event: %s" s))
3535-3636-let event_jsont : event Jsont.t =
3737- Jsont.enum
3838- [
3939- ("PreToolUse", Pre_tool_use);
4040- ("PostToolUse", Post_tool_use);
4141- ("UserPromptSubmit", User_prompt_submit);
4242- ("Stop", Stop);
4343- ("SubagentStop", Subagent_stop);
4444- ("PreCompact", Pre_compact);
4545- ]
4646-4747-(** Context provided to hook callbacks *)
4848-module Context = struct
4949- type t = { signal : unit option; unknown : Unknown.t }
5050-5151- let create ?signal () =
5252- let signal = Option.map (fun () -> ()) signal in
5353- { signal; unknown = Unknown.empty }
5454-5555- let signal t = t.signal
5656- let unknown t = t.unknown
5757-5858- let jsont : t Jsont.t =
5959- let make unknown = { signal = None; unknown } in
6060- Jsont.Object.map ~kind:"Context" make
6161- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
6262- |> Jsont.Object.finish
6363-end
6464-6565-(** Hook decision control *)
6666-type decision = Continue | Block
6767-6868-let decision_jsont : decision Jsont.t =
6969- Jsont.enum [ ("continue", Continue); ("block", Block) ]
7070-7171-type result = {
7272- decision : decision option;
7373- system_message : string option;
7474- hook_specific_output : Jsont.json option;
7575- unknown : Unknown.t;
7676-}
7777-(** Generic hook result *)
7878-7979-let result_jsont : result Jsont.t =
8080- let make decision system_message hook_specific_output unknown =
8181- { decision; system_message; hook_specific_output; unknown }
8282- in
8383- Jsont.Object.map ~kind:"Result" make
8484- |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun r -> r.decision)
8585- |> Jsont.Object.opt_mem "systemMessage" Jsont.string ~enc:(fun r ->
8686- r.system_message)
8787- |> Jsont.Object.opt_mem "hookSpecificOutput" Jsont.json ~enc:(fun r ->
8888- r.hook_specific_output)
8989- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun r -> r.unknown)
9090- |> Jsont.Object.finish
9191-9292-(** {1 PreToolUse Hook} *)
9393-module PreToolUse = struct
9494- module Input = struct
9595- type t = {
9696- session_id : string;
9797- transcript_path : string;
9898- tool_name : string;
9999- tool_input : Jsont.json;
100100- unknown : Unknown.t;
101101- }
102102-103103- let session_id t = t.session_id
104104- let transcript_path t = t.transcript_path
105105- let tool_name t = t.tool_name
106106- let tool_input t = t.tool_input
107107- let unknown t = t.unknown
108108-109109- let jsont : t Jsont.t =
110110- let make session_id transcript_path tool_name tool_input unknown =
111111- { session_id; transcript_path; tool_name; tool_input; unknown }
112112- in
113113- Jsont.Object.map ~kind:"PreToolUseInput" make
114114- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
115115- |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
116116- |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
117117- |> Jsont.Object.mem "tool_input" Jsont.json ~enc:tool_input
118118- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
119119- |> Jsont.Object.finish
120120- end
121121-122122- type permission_decision = [ `Allow | `Deny | `Ask ]
123123-124124- let permission_decision_jsont : permission_decision Jsont.t =
125125- Jsont.enum [ ("allow", `Allow); ("deny", `Deny); ("ask", `Ask) ]
126126-127127- module Output = struct
128128- type t = {
129129- permission_decision : permission_decision option;
130130- permission_decision_reason : string option;
131131- updated_input : Jsont.json option;
132132- unknown : Unknown.t;
133133- }
134134-135135- let jsont : t Jsont.t =
136136- let make _hook_event_name permission_decision permission_decision_reason
137137- updated_input unknown =
138138- {
139139- permission_decision;
140140- permission_decision_reason;
141141- updated_input;
142142- unknown;
143143- }
144144- in
145145- Jsont.Object.map ~kind:"PreToolUseOutput" make
146146- |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun _ ->
147147- "PreToolUse")
148148- |> Jsont.Object.opt_mem "permissionDecision" permission_decision_jsont
149149- ~enc:(fun o -> o.permission_decision)
150150- |> Jsont.Object.opt_mem "permissionDecisionReason" Jsont.string
151151- ~enc:(fun o -> o.permission_decision_reason)
152152- |> Jsont.Object.opt_mem "updatedInput" Jsont.json ~enc:(fun o ->
153153- o.updated_input)
154154- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun o -> o.unknown)
155155- |> Jsont.Object.finish
156156-157157- let allow ?reason ?updated_input () =
158158- {
159159- permission_decision = Some `Allow;
160160- permission_decision_reason = reason;
161161- updated_input;
162162- unknown = Unknown.empty;
163163- }
164164-165165- let deny ?reason () =
166166- {
167167- permission_decision = Some `Deny;
168168- permission_decision_reason = reason;
169169- updated_input = None;
170170- unknown = Unknown.empty;
171171- }
172172-173173- let ask ?reason () =
174174- {
175175- permission_decision = Some `Ask;
176176- permission_decision_reason = reason;
177177- updated_input = None;
178178- unknown = Unknown.empty;
179179- }
180180-181181- let continue () =
182182- {
183183- permission_decision = None;
184184- permission_decision_reason = None;
185185- updated_input = None;
186186- unknown = Unknown.empty;
187187- }
188188- end
189189-end
190190-191191-(** {1 PostToolUse Hook} *)
192192-module PostToolUse = struct
193193- module Input = struct
194194- type t = {
195195- session_id : string;
196196- transcript_path : string;
197197- tool_name : string;
198198- tool_input : Jsont.json;
199199- tool_response : Jsont.json;
200200- unknown : Unknown.t;
201201- }
202202-203203- let session_id t = t.session_id
204204- let transcript_path t = t.transcript_path
205205- let tool_name t = t.tool_name
206206- let tool_input t = t.tool_input
207207- let tool_response t = t.tool_response
208208- let unknown t = t.unknown
209209-210210- let jsont : t Jsont.t =
211211- let make session_id transcript_path tool_name tool_input tool_response
212212- unknown =
213213- {
214214- session_id;
215215- transcript_path;
216216- tool_name;
217217- tool_input;
218218- tool_response;
219219- unknown;
220220- }
221221- in
222222- Jsont.Object.map ~kind:"PostToolUseInput" make
223223- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
224224- |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
225225- |> Jsont.Object.mem "tool_name" Jsont.string ~enc:tool_name
226226- |> Jsont.Object.mem "tool_input" Jsont.json ~enc:tool_input
227227- |> Jsont.Object.mem "tool_response" Jsont.json ~enc:tool_response
228228- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
229229- |> Jsont.Object.finish
230230- end
231231-232232- module Output = struct
233233- type t = {
234234- decision : decision option;
235235- reason : string option;
236236- additional_context : string option;
237237- unknown : Unknown.t;
238238- }
239239-240240- let jsont : t Jsont.t =
241241- let make _hook_event_name decision reason additional_context unknown =
242242- { decision; reason; additional_context; unknown }
243243- in
244244- Jsont.Object.map ~kind:"PostToolUseOutput" make
245245- |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun _ ->
246246- "PostToolUse")
247247- |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o ->
248248- o.decision)
249249- |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
250250- |> Jsont.Object.opt_mem "additionalContext" Jsont.string ~enc:(fun o ->
251251- o.additional_context)
252252- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun o -> o.unknown)
253253- |> Jsont.Object.finish
254254-255255- let continue ?additional_context () =
256256- {
257257- decision = None;
258258- reason = None;
259259- additional_context;
260260- unknown = Unknown.empty;
261261- }
262262-263263- let block ?reason ?additional_context () =
264264- {
265265- decision = Some Block;
266266- reason;
267267- additional_context;
268268- unknown = Unknown.empty;
269269- }
270270- end
271271-end
272272-273273-(** {1 UserPromptSubmit Hook} *)
274274-module UserPromptSubmit = struct
275275- module Input = struct
276276- type t = {
277277- session_id : string;
278278- transcript_path : string;
279279- prompt : string;
280280- unknown : Unknown.t;
281281- }
282282-283283- let session_id t = t.session_id
284284- let transcript_path t = t.transcript_path
285285- let prompt t = t.prompt
286286- let unknown t = t.unknown
287287-288288- let jsont : t Jsont.t =
289289- let make session_id transcript_path prompt unknown =
290290- { session_id; transcript_path; prompt; unknown }
291291- in
292292- Jsont.Object.map ~kind:"UserPromptSubmitInput" make
293293- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
294294- |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
295295- |> Jsont.Object.mem "prompt" Jsont.string ~enc:prompt
296296- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
297297- |> Jsont.Object.finish
298298- end
299299-300300- module Output = struct
301301- type t = {
302302- decision : decision option;
303303- reason : string option;
304304- additional_context : string option;
305305- unknown : Unknown.t;
306306- }
307307-308308- let jsont : t Jsont.t =
309309- let make _hook_event_name decision reason additional_context unknown =
310310- { decision; reason; additional_context; unknown }
311311- in
312312- Jsont.Object.map ~kind:"UserPromptSubmitOutput" make
313313- |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun _ ->
314314- "UserPromptSubmit")
315315- |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o ->
316316- o.decision)
317317- |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
318318- |> Jsont.Object.opt_mem "additionalContext" Jsont.string ~enc:(fun o ->
319319- o.additional_context)
320320- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun o -> o.unknown)
321321- |> Jsont.Object.finish
322322-323323- let continue ?additional_context () =
324324- {
325325- decision = None;
326326- reason = None;
327327- additional_context;
328328- unknown = Unknown.empty;
329329- }
330330-331331- let block ?reason () =
332332- {
333333- decision = Some Block;
334334- reason;
335335- additional_context = None;
336336- unknown = Unknown.empty;
337337- }
338338- end
339339-end
340340-341341-(** {1 Stop Hook} *)
342342-module Stop = struct
343343- module Input = struct
344344- type t = {
345345- session_id : string;
346346- transcript_path : string;
347347- stop_hook_active : bool;
348348- unknown : Unknown.t;
349349- }
350350-351351- let session_id t = t.session_id
352352- let transcript_path t = t.transcript_path
353353- let stop_hook_active t = t.stop_hook_active
354354- let unknown t = t.unknown
355355-356356- let jsont : t Jsont.t =
357357- let make session_id transcript_path stop_hook_active unknown =
358358- { session_id; transcript_path; stop_hook_active; unknown }
359359- in
360360- Jsont.Object.map ~kind:"StopInput" make
361361- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
362362- |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
363363- |> Jsont.Object.mem "stop_hook_active" Jsont.bool ~enc:stop_hook_active
364364- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
365365- |> Jsont.Object.finish
366366- end
367367-368368- module Output = struct
369369- type t = {
370370- decision : decision option;
371371- reason : string option;
372372- unknown : Unknown.t;
373373- }
374374-375375- let jsont : t Jsont.t =
376376- let make _hook_event_name decision reason unknown =
377377- { decision; reason; unknown }
378378- in
379379- Jsont.Object.map ~kind:"StopOutput" make
380380- |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun _ -> "Stop")
381381- |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun o ->
382382- o.decision)
383383- |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun o -> o.reason)
384384- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun o -> o.unknown)
385385- |> Jsont.Object.finish
386386-387387- let continue () =
388388- { decision = None; reason = None; unknown = Unknown.empty }
389389-390390- let block ?reason () =
391391- { decision = Some Block; reason; unknown = Unknown.empty }
392392- end
393393-end
394394-395395-(** {1 SubagentStop Hook} - Same structure as Stop *)
396396-module SubagentStop = struct
397397- module Input = struct
398398- type t = Stop.Input.t
399399-400400- let jsont = Stop.Input.jsont
401401- let session_id = Stop.Input.session_id
402402- let transcript_path = Stop.Input.transcript_path
403403- let stop_hook_active = Stop.Input.stop_hook_active
404404- let unknown = Stop.Input.unknown
405405- end
406406-407407- module Output = struct
408408- type t = Stop.Output.t
409409-410410- let jsont : t Jsont.t =
411411- let make _hook_event_name decision reason unknown : t =
412412- { Stop.Output.decision; reason; unknown }
413413- in
414414- Jsont.Object.map ~kind:"SubagentStopOutput" make
415415- |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun _ ->
416416- "SubagentStop")
417417- |> Jsont.Object.opt_mem "decision" decision_jsont ~enc:(fun (o : t) ->
418418- o.Stop.Output.decision)
419419- |> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun (o : t) ->
420420- o.Stop.Output.reason)
421421- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (o : t) ->
422422- o.Stop.Output.unknown)
423423- |> Jsont.Object.finish
424424-425425- let continue = Stop.Output.continue
426426- let block = Stop.Output.block
427427- end
428428-end
429429-430430-(** {1 PreCompact Hook} *)
431431-module PreCompact = struct
432432- module Input = struct
433433- type t = {
434434- session_id : string;
435435- transcript_path : string;
436436- unknown : Unknown.t;
437437- }
438438-439439- let session_id t = t.session_id
440440- let transcript_path t = t.transcript_path
441441- let unknown t = t.unknown
442442-443443- let jsont : t Jsont.t =
444444- let make session_id transcript_path unknown =
445445- { session_id; transcript_path; unknown }
446446- in
447447- Jsont.Object.map ~kind:"PreCompactInput" make
448448- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
449449- |> Jsont.Object.mem "transcript_path" Jsont.string ~enc:transcript_path
450450- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
451451- |> Jsont.Object.finish
452452- end
453453-454454- module Output = struct
455455- type t = unit
456456-457457- let jsont : t Jsont.t =
458458- Jsont.Object.map ~kind:"PreCompactOutput" (fun _hook_event_name -> ())
459459- |> Jsont.Object.mem "hookEventName" Jsont.string ~enc:(fun () ->
460460- "PreCompact")
461461- |> Jsont.Object.finish
462462-463463- let continue () = ()
464464- end
465465-end
466466-467467-(** {1 Result Builders} *)
468468-let continue ?system_message ?hook_specific_output () =
469469- {
470470- decision = None;
471471- system_message;
472472- hook_specific_output;
473473- unknown = Unknown.empty;
474474- }
475475-476476-let block ?system_message ?hook_specific_output () =
477477- {
478478- decision = Some Block;
479479- system_message;
480480- hook_specific_output;
481481- unknown = Unknown.empty;
482482- }
-363
proto/hooks.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Claude Code Hooks System - Wire Format
77-88- This module defines the wire format for hook configuration. Hooks allow you
99- to intercept and control events in Claude Code sessions, such as tool usage,
1010- prompt submission, and session stops.
1111-1212- {1 Overview}
1313-1414- Hooks are organized by event type, with each event having:
1515- - A typed input structure (accessible via submodules)
1616- - A typed output structure for responses
1717- - Helper functions for common responses
1818-1919- This is the wire format module - it does not include the callback system or
2020- Eio dependencies. For the full hooks system with callbacks, see the [Hooks]
2121- module in the [lib] directory. *)
2222-2323-(** {1 Hook Events} *)
2424-2525-(** Hook event types *)
2626-type event =
2727- | Pre_tool_use (** Fires before a tool is executed *)
2828- | Post_tool_use (** Fires after a tool completes *)
2929- | User_prompt_submit (** Fires when user submits a prompt *)
3030- | Stop (** Fires when conversation stops *)
3131- | Subagent_stop (** Fires when a subagent stops *)
3232- | Pre_compact (** Fires before message compaction *)
3333-3434-val event_to_string : event -> string
3535-(** [event_to_string event] converts an event to its wire format string. Wire
3636- format: "PreToolUse", "PostToolUse", "UserPromptSubmit", "Stop",
3737- "SubagentStop", "PreCompact" *)
3838-3939-val event_of_string : string -> event
4040-(** [event_of_string s] parses an event from its wire format string.
4141- @raise Invalid_argument if the string is not a valid event. *)
4242-4343-val event_jsont : event Jsont.t
4444-(** [event_jsont] is the Jsont codec for hook events. *)
4545-4646-(** {1 Context} *)
4747-4848-module Context : sig
4949- (** Context provided to hook callbacks. *)
5050-5151- type t
5252- (** The type of hook context. *)
5353-5454- val jsont : t Jsont.t
5555- (** [jsont] is the Jsont codec for hook context. Preserves unknown fields. *)
5656-5757- val create : ?signal:unit -> unit -> t
5858- (** [create ?signal ()] creates a new context.
5959- @param signal Optional abort signal support (future use) *)
6060-6161- val signal : t -> unit option
6262- (** [signal t] returns the optional abort signal. *)
6363-6464- val unknown : t -> Unknown.t
6565- (** [unknown t] returns the unknown fields. *)
6666-end
6767-6868-(** {1 Decisions} *)
6969-7070-(** Hook decision control *)
7171-type decision =
7272- | Continue (** Allow the action to proceed *)
7373- | Block (** Block the action *)
7474-7575-val decision_jsont : decision Jsont.t
7676-(** [decision_jsont] is the Jsont codec for hook decisions. Wire format:
7777- "continue", "block" *)
7878-7979-(** {1 Typed Hook Modules} *)
8080-8181-(** PreToolUse hook - fires before tool execution *)
8282-module PreToolUse : sig
8383- (** {2 Input} *)
8484-8585- module Input : sig
8686- type t
8787- (** Typed input for PreToolUse hooks *)
8888-8989- val jsont : t Jsont.t
9090- (** [jsont] is the Jsont codec for PreToolUse input. *)
9191-9292- val session_id : t -> string
9393- (** [session_id t] returns the session ID. *)
9494-9595- val transcript_path : t -> string
9696- (** [transcript_path t] returns the transcript file path. *)
9797-9898- val tool_name : t -> string
9999- (** [tool_name t] returns the tool name being invoked. *)
100100-101101- val tool_input : t -> Jsont.json
102102- (** [tool_input t] returns the tool's input as raw JSON. *)
103103-104104- val unknown : t -> Unknown.t
105105- (** [unknown t] returns the unknown fields. *)
106106- end
107107-108108- (** {2 Output} *)
109109-110110- type permission_decision = [ `Allow | `Deny | `Ask ]
111111- (** Permission decision for tool usage. Wire format: "allow", "deny", "ask" *)
112112-113113- val permission_decision_jsont : permission_decision Jsont.t
114114- (** [permission_decision_jsont] is the Jsont codec for permission decisions.
115115- *)
116116-117117- module Output : sig
118118- type t
119119- (** Typed output for PreToolUse hooks *)
120120-121121- val jsont : t Jsont.t
122122- (** [jsont] is the Jsont codec for PreToolUse output. *)
123123-124124- val allow : ?reason:string -> ?updated_input:Jsont.json -> unit -> t
125125- (** [allow ?reason ?updated_input ()] creates an allow response.
126126- @param reason Optional explanation for allowing
127127- @param updated_input Optional modified tool input *)
128128-129129- val deny : ?reason:string -> unit -> t
130130- (** [deny ?reason ()] creates a deny response.
131131- @param reason Optional explanation for denying *)
132132-133133- val ask : ?reason:string -> unit -> t
134134- (** [ask ?reason ()] creates an ask response to prompt the user.
135135- @param reason Optional explanation for asking *)
136136-137137- val continue : unit -> t
138138- (** [continue ()] creates a continue response with no decision. *)
139139- end
140140-end
141141-142142-(** PostToolUse hook - fires after tool execution *)
143143-module PostToolUse : sig
144144- (** {2 Input} *)
145145-146146- module Input : sig
147147- type t
148148- (** Typed input for PostToolUse hooks *)
149149-150150- val jsont : t Jsont.t
151151- (** [jsont] is the Jsont codec for PostToolUse input. *)
152152-153153- val session_id : t -> string
154154- (** [session_id t] returns the session ID. *)
155155-156156- val transcript_path : t -> string
157157- (** [transcript_path t] returns the transcript file path. *)
158158-159159- val tool_name : t -> string
160160- (** [tool_name t] returns the tool name that was invoked. *)
161161-162162- val tool_input : t -> Jsont.json
163163- (** [tool_input t] returns the tool's input as raw JSON. *)
164164-165165- val tool_response : t -> Jsont.json
166166- (** [tool_response t] returns the tool's response as raw JSON. *)
167167-168168- val unknown : t -> Unknown.t
169169- (** [unknown t] returns the unknown fields. *)
170170- end
171171-172172- (** {2 Output} *)
173173-174174- module Output : sig
175175- type t
176176- (** Typed output for PostToolUse hooks *)
177177-178178- val jsont : t Jsont.t
179179- (** [jsont] is the Jsont codec for PostToolUse output. *)
180180-181181- val continue : ?additional_context:string -> unit -> t
182182- (** [continue ?additional_context ()] creates a continue response.
183183- @param additional_context Optional context to add to the transcript *)
184184-185185- val block : ?reason:string -> ?additional_context:string -> unit -> t
186186- (** [block ?reason ?additional_context ()] creates a block response.
187187- @param reason Optional explanation for blocking
188188- @param additional_context Optional context to add to the transcript *)
189189- end
190190-end
191191-192192-(** UserPromptSubmit hook - fires when user submits a prompt *)
193193-module UserPromptSubmit : sig
194194- (** {2 Input} *)
195195-196196- module Input : sig
197197- type t
198198- (** Typed input for UserPromptSubmit hooks *)
199199-200200- val jsont : t Jsont.t
201201- (** [jsont] is the Jsont codec for UserPromptSubmit input. *)
202202-203203- val session_id : t -> string
204204- (** [session_id t] returns the session ID. *)
205205-206206- val transcript_path : t -> string
207207- (** [transcript_path t] returns the transcript file path. *)
208208-209209- val prompt : t -> string
210210- (** [prompt t] returns the user's prompt text. *)
211211-212212- val unknown : t -> Unknown.t
213213- (** [unknown t] returns the unknown fields. *)
214214- end
215215-216216- (** {2 Output} *)
217217-218218- module Output : sig
219219- type t
220220- (** Typed output for UserPromptSubmit hooks *)
221221-222222- val jsont : t Jsont.t
223223- (** [jsont] is the Jsont codec for UserPromptSubmit output. *)
224224-225225- val continue : ?additional_context:string -> unit -> t
226226- (** [continue ?additional_context ()] creates a continue response.
227227- @param additional_context Optional context to add to the transcript *)
228228-229229- val block : ?reason:string -> unit -> t
230230- (** [block ?reason ()] creates a block response.
231231- @param reason Optional explanation for blocking *)
232232- end
233233-end
234234-235235-(** Stop hook - fires when conversation stops *)
236236-module Stop : sig
237237- (** {2 Input} *)
238238-239239- module Input : sig
240240- type t
241241- (** Typed input for Stop hooks *)
242242-243243- val jsont : t Jsont.t
244244- (** [jsont] is the Jsont codec for Stop input. *)
245245-246246- val session_id : t -> string
247247- (** [session_id t] returns the session ID. *)
248248-249249- val transcript_path : t -> string
250250- (** [transcript_path t] returns the transcript file path. *)
251251-252252- val stop_hook_active : t -> bool
253253- (** [stop_hook_active t] returns whether stop hooks are active. *)
254254-255255- val unknown : t -> Unknown.t
256256- (** [unknown t] returns the unknown fields. *)
257257- end
258258-259259- (** {2 Output} *)
260260-261261- module Output : sig
262262- type t
263263- (** Typed output for Stop hooks *)
264264-265265- val jsont : t Jsont.t
266266- (** [jsont] is the Jsont codec for Stop output. *)
267267-268268- val continue : unit -> t
269269- (** [continue ()] creates a continue response. *)
270270-271271- val block : ?reason:string -> unit -> t
272272- (** [block ?reason ()] creates a block response.
273273- @param reason Optional explanation for blocking *)
274274- end
275275-end
276276-277277-(** SubagentStop hook - fires when a subagent stops *)
278278-module SubagentStop : sig
279279- (** {2 Input} *)
280280-281281- module Input : sig
282282- type t = Stop.Input.t
283283- (** Same structure as Stop.Input *)
284284-285285- val jsont : t Jsont.t
286286- val session_id : t -> string
287287- val transcript_path : t -> string
288288- val stop_hook_active : t -> bool
289289- val unknown : t -> Unknown.t
290290- end
291291-292292- (** {2 Output} *)
293293-294294- module Output : sig
295295- type t = Stop.Output.t
296296- (** Same structure as Stop.Output *)
297297-298298- val jsont : t Jsont.t
299299- val continue : unit -> t
300300- val block : ?reason:string -> unit -> t
301301- end
302302-end
303303-304304-(** PreCompact hook - fires before message compaction *)
305305-module PreCompact : sig
306306- (** {2 Input} *)
307307-308308- module Input : sig
309309- type t
310310- (** Typed input for PreCompact hooks *)
311311-312312- val jsont : t Jsont.t
313313- (** [jsont] is the Jsont codec for PreCompact input. *)
314314-315315- val session_id : t -> string
316316- (** [session_id t] returns the session ID. *)
317317-318318- val transcript_path : t -> string
319319- (** [transcript_path t] returns the transcript file path. *)
320320-321321- val unknown : t -> Unknown.t
322322- (** [unknown t] returns the unknown fields. *)
323323- end
324324-325325- (** {2 Output} *)
326326-327327- module Output : sig
328328- type t = unit
329329- (** PreCompact has no specific output *)
330330-331331- val jsont : t Jsont.t
332332- (** [jsont] is the Jsont codec for PreCompact output (unit codec). *)
333333-334334- val continue : unit -> t
335335- (** [continue ()] returns unit. *)
336336- end
337337-end
338338-339339-(** {1 Generic Hook Result} *)
340340-341341-type result = {
342342- decision : decision option;
343343- system_message : string option;
344344- hook_specific_output : Jsont.json option;
345345- unknown : Unknown.t;
346346-}
347347-(** Generic result structure for hooks *)
348348-349349-val result_jsont : result Jsont.t
350350-(** [result_jsont] is the Jsont codec for hook results. *)
351351-352352-val continue :
353353- ?system_message:string -> ?hook_specific_output:Jsont.json -> unit -> result
354354-(** [continue ?system_message ?hook_specific_output ()] creates a continue
355355- result.
356356- @param system_message Optional message to add to system context
357357- @param hook_specific_output Optional hook-specific output data *)
358358-359359-val block :
360360- ?system_message:string -> ?hook_specific_output:Jsont.json -> unit -> result
361361-(** [block ?system_message ?hook_specific_output ()] creates a block result.
362362- @param system_message Optional message to add to system context
363363- @param hook_specific_output Optional hook-specific output data *)
-72
proto/incoming.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Incoming messages from Claude CLI.
77-88- This uses the Control module's request_envelope_jsont and
99- response_envelope_jsont for control messages, and Message.jsont for
1010- conversation messages. The top-level discriminator is the "type" field. *)
1111-1212-type t =
1313- | Message of Message.t
1414- | Control_response of Control.response_envelope
1515- | Control_request of Control.request_envelope
1616-1717-let jsont : t Jsont.t =
1818- (* Message types use "user", "assistant", "system", "result" as type values.
1919- Control uses "control_request" and "control_response".
2020-2121- We use case_mem for all types. Note: we use the inner message codecs
2222- (User.incoming_jsont, etc.) rather than Message.jsont to avoid nesting
2323- case_mem on the same "type" field. *)
2424- let case_control_request =
2525- Jsont.Object.Case.map "control_request" Control.request_envelope_jsont
2626- ~dec:(fun v -> Control_request v)
2727- in
2828- let case_control_response =
2929- Jsont.Object.Case.map "control_response" Control.response_envelope_jsont
3030- ~dec:(fun v -> Control_response v)
3131- in
3232- let case_user =
3333- Jsont.Object.Case.map "user" Message.User.incoming_jsont ~dec:(fun v ->
3434- Message (Message.User v))
3535- in
3636- let case_assistant =
3737- Jsont.Object.Case.map "assistant" Message.Assistant.incoming_jsont
3838- ~dec:(fun v -> Message (Message.Assistant v))
3939- in
4040- let case_system =
4141- Jsont.Object.Case.map "system" Message.System.jsont ~dec:(fun v ->
4242- Message (Message.System v))
4343- in
4444- let case_result =
4545- Jsont.Object.Case.map "result" Message.Result.jsont ~dec:(fun v ->
4646- Message (Message.Result v))
4747- in
4848- let enc_case = function
4949- | Control_request v -> Jsont.Object.Case.value case_control_request v
5050- | Control_response v -> Jsont.Object.Case.value case_control_response v
5151- | Message msg -> (
5252- match msg with
5353- | Message.User u -> Jsont.Object.Case.value case_user u
5454- | Message.Assistant a -> Jsont.Object.Case.value case_assistant a
5555- | Message.System s -> Jsont.Object.Case.value case_system s
5656- | Message.Result r -> Jsont.Object.Case.value case_result r)
5757- in
5858- let cases =
5959- Jsont.Object.Case.
6060- [
6161- make case_control_request;
6262- make case_control_response;
6363- make case_user;
6464- make case_assistant;
6565- make case_system;
6666- make case_result;
6767- ]
6868- in
6969- Jsont.Object.map ~kind:"Incoming" Fun.id
7070- |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
7171- ~tag_to_string:Fun.id ~tag_compare:String.compare
7272- |> Jsont.Object.finish
-26
proto/incoming.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Incoming messages from the Claude CLI.
77-88- This module defines a discriminated union of all possible message types that
99- can be received from the Claude CLI, with a single jsont codec.
1010-1111- The codec uses the "type" field to discriminate between message types:
1212- - "user", "assistant", "system", "result" -> Message variant
1313- - "control_response" -> Control_response variant
1414- - "control_request" -> Control_request variant
1515-1616- This provides a clean, type-safe way to decode incoming messages in a single
1717- operation. *)
1818-1919-type t =
2020- | Message of Message.t
2121- | Control_response of Control.response_envelope
2222- | Control_request of Control.request_envelope
2323-2424-val jsont : t Jsont.t
2525-(** Codec for incoming messages. Uses the "type" field to discriminate. Use
2626- [Jsont.pp_value jsont ()] for pretty-printing. *)
-372
proto/message.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-module User = struct
77- type content = String of string | Blocks of Content_block.t list
88- type t = { content : content; unknown : Unknown.t }
99-1010- let create_string s = { content = String s; unknown = Unknown.empty }
1111-1212- let create_blocks blocks =
1313- { content = Blocks blocks; unknown = Unknown.empty }
1414-1515- let create_with_tool_result ~tool_use_id ~content ?is_error () =
1616- let tool_result =
1717- Content_block.tool_result ~tool_use_id ~content ?is_error ()
1818- in
1919- { content = Blocks [ tool_result ]; unknown = Unknown.empty }
2020-2121- let make content unknown = { content; unknown }
2222- let content t = t.content
2323- let unknown t = t.unknown
2424-2525- (* Decode content from json value *)
2626- let decode_content json =
2727- match json with
2828- | Jsont.String (s, _) -> String s
2929- | Jsont.Array (items, _) ->
3030- let blocks =
3131- List.map
3232- (fun j ->
3333- match Jsont.Json.decode Content_block.jsont j with
3434- | Ok v -> v
3535- | Error e -> invalid_arg ("Invalid content block: " ^ e))
3636- items
3737- in
3838- Blocks blocks
3939- | _ -> failwith "Content must be string or array"
4040-4141- (* Encode content to json value *)
4242- let encode_content = function
4343- | String s -> Jsont.String (s, Jsont.Meta.none)
4444- | Blocks blocks ->
4545- let jsons =
4646- List.map
4747- (fun b ->
4848- match Jsont.Json.encode Content_block.jsont b with
4949- | Ok json -> json
5050- | Error e -> invalid_arg ("encode_content: " ^ e))
5151- blocks
5252- in
5353- Jsont.Array (jsons, Jsont.Meta.none)
5454-5555- let jsont : t Jsont.t =
5656- Jsont.Object.map ~kind:"User" (fun json_content unknown ->
5757- let content = decode_content json_content in
5858- make content unknown)
5959- |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
6060- encode_content (content t))
6161- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
6262- |> Jsont.Object.finish
6363-6464- (* Jsont codec for parsing incoming user messages from CLI *)
6565- let incoming_jsont : t Jsont.t =
6666- let message_jsont =
6767- Jsont.Object.map ~kind:"UserMessage" (fun json_content ->
6868- let content = decode_content json_content in
6969- { content; unknown = Unknown.empty })
7070- |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
7171- encode_content (content t))
7272- |> Jsont.Object.finish
7373- in
7474- Jsont.Object.map ~kind:"UserEnvelope" Fun.id
7575- |> Jsont.Object.mem "message" message_jsont ~enc:Fun.id
7676- |> Jsont.Object.finish
7777-7878- (* Jsont codec for outgoing user messages - wraps in message envelope *)
7979- let outgoing_jsont : t Jsont.t =
8080- (* The inner message object with role and content *)
8181- let message_jsont =
8282- Jsont.Object.map ~kind:"UserOutgoingMessage" (fun _role json_content ->
8383- let content = decode_content json_content in
8484- { content; unknown = Unknown.empty })
8585- |> Jsont.Object.mem "role" Jsont.string ~enc:(fun _ -> "user")
8686- |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
8787- encode_content (content t))
8888- |> Jsont.Object.finish
8989- in
9090- Jsont.Object.map ~kind:"UserOutgoingEnvelope" Fun.id
9191- |> Jsont.Object.mem "message" message_jsont ~enc:Fun.id
9292- |> Jsont.Object.finish
9393-end
9494-9595-module Assistant = struct
9696- type error =
9797- [ `Authentication_failed
9898- | `Billing_error
9999- | `Rate_limit
100100- | `Invalid_request
101101- | `Server_error
102102- | `Unknown ]
103103-104104- let error_jsont : error Jsont.t =
105105- Jsont.enum
106106- [
107107- ("authentication_failed", `Authentication_failed);
108108- ("billing_error", `Billing_error);
109109- ("rate_limit", `Rate_limit);
110110- ("invalid_request", `Invalid_request);
111111- ("server_error", `Server_error);
112112- ("unknown", `Unknown);
113113- ]
114114-115115- type t = {
116116- content : Content_block.t list;
117117- model : string;
118118- error : error option;
119119- unknown : Unknown.t;
120120- }
121121-122122- let create ~content ~model ?error () =
123123- { content; model; error; unknown = Unknown.empty }
124124-125125- let make content model error unknown = { content; model; error; unknown }
126126- let content t = t.content
127127- let model t = t.model
128128- let error t = t.error
129129- let unknown t = t.unknown
130130-131131- let jsont : t Jsont.t =
132132- Jsont.Object.map ~kind:"Assistant" make
133133- |> Jsont.Object.mem "content" (Jsont.list Content_block.jsont) ~enc:content
134134- |> Jsont.Object.mem "model" Jsont.string ~enc:model
135135- |> Jsont.Object.opt_mem "error" error_jsont ~enc:error
136136- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
137137- |> Jsont.Object.finish
138138-139139- (* Jsont codec for parsing incoming assistant messages from CLI *)
140140- let incoming_jsont : t Jsont.t =
141141- Jsont.Object.map ~kind:"AssistantEnvelope" Fun.id
142142- |> Jsont.Object.mem "message" jsont ~enc:Fun.id
143143- |> Jsont.Object.finish
144144-end
145145-146146-module System = struct
147147- (** System messages as a discriminated union on "subtype" field *)
148148-149149- type init = {
150150- session_id : string option;
151151- model : string option;
152152- cwd : string option;
153153- unknown : Unknown.t;
154154- }
155155-156156- type error = { error : string; unknown : Unknown.t }
157157- type t = Init of init | Error of error
158158-159159- (* Accessors *)
160160- let session_id = function Init i -> i.session_id | _ -> None
161161- let model = function Init i -> i.model | _ -> None
162162- let cwd = function Init i -> i.cwd | _ -> None
163163- let error_msg = function Error e -> Some e.error | _ -> None
164164- let unknown = function Init i -> i.unknown | Error e -> e.unknown
165165-166166- (* Constructors *)
167167- let init ?session_id ?model ?cwd () =
168168- Init { session_id; model; cwd; unknown = Unknown.empty }
169169-170170- let error ~error = Error { error; unknown = Unknown.empty }
171171-172172- (* Individual record codecs *)
173173- let init_jsont : init Jsont.t =
174174- let make session_id model cwd unknown : init =
175175- { session_id; model; cwd; unknown }
176176- in
177177- Jsont.Object.map ~kind:"SystemInit" make
178178- |> Jsont.Object.opt_mem "session_id" Jsont.string ~enc:(fun (r : init) ->
179179- r.session_id)
180180- |> Jsont.Object.opt_mem "model" Jsont.string ~enc:(fun (r : init) ->
181181- r.model)
182182- |> Jsont.Object.opt_mem "cwd" Jsont.string ~enc:(fun (r : init) -> r.cwd)
183183- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : init) -> r.unknown)
184184- |> Jsont.Object.finish
185185-186186- let error_jsont : error Jsont.t =
187187- let make err unknown : error = { error = err; unknown } in
188188- Jsont.Object.map ~kind:"SystemError" make
189189- |> Jsont.Object.mem "error" Jsont.string ~enc:(fun (r : error) -> r.error)
190190- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : error) ->
191191- r.unknown)
192192- |> Jsont.Object.finish
193193-194194- (* Main codec using case_mem for "subtype" discriminator *)
195195- let jsont : t Jsont.t =
196196- let case_init =
197197- Jsont.Object.Case.map "init" init_jsont ~dec:(fun v -> Init v)
198198- in
199199- let case_error =
200200- Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v)
201201- in
202202- let enc_case = function
203203- | Init v -> Jsont.Object.Case.value case_init v
204204- | Error v -> Jsont.Object.Case.value case_error v
205205- in
206206- let cases = Jsont.Object.Case.[ make case_init; make case_error ] in
207207- Jsont.Object.map ~kind:"System" Fun.id
208208- |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
209209- ~tag_to_string:Fun.id ~tag_compare:String.compare
210210- |> Jsont.Object.finish
211211-end
212212-213213-module Result = struct
214214- module Usage = struct
215215- type t = {
216216- input_tokens : int option;
217217- output_tokens : int option;
218218- total_tokens : int option;
219219- cache_creation_input_tokens : int option;
220220- cache_read_input_tokens : int option;
221221- unknown : Unknown.t;
222222- }
223223-224224- let make input_tokens output_tokens total_tokens cache_creation_input_tokens
225225- cache_read_input_tokens unknown =
226226- {
227227- input_tokens;
228228- output_tokens;
229229- total_tokens;
230230- cache_creation_input_tokens;
231231- cache_read_input_tokens;
232232- unknown;
233233- }
234234-235235- let create ?input_tokens ?output_tokens ?total_tokens
236236- ?cache_creation_input_tokens ?cache_read_input_tokens () =
237237- {
238238- input_tokens;
239239- output_tokens;
240240- total_tokens;
241241- cache_creation_input_tokens;
242242- cache_read_input_tokens;
243243- unknown = Unknown.empty;
244244- }
245245-246246- let input_tokens t = t.input_tokens
247247- let output_tokens t = t.output_tokens
248248- let total_tokens t = t.total_tokens
249249- let cache_creation_input_tokens t = t.cache_creation_input_tokens
250250- let cache_read_input_tokens t = t.cache_read_input_tokens
251251- let unknown t = t.unknown
252252-253253- let jsont : t Jsont.t =
254254- Jsont.Object.map ~kind:"Usage" make
255255- |> Jsont.Object.opt_mem "input_tokens" Jsont.int ~enc:input_tokens
256256- |> Jsont.Object.opt_mem "output_tokens" Jsont.int ~enc:output_tokens
257257- |> Jsont.Object.opt_mem "total_tokens" Jsont.int ~enc:total_tokens
258258- |> Jsont.Object.opt_mem "cache_creation_input_tokens" Jsont.int
259259- ~enc:cache_creation_input_tokens
260260- |> Jsont.Object.opt_mem "cache_read_input_tokens" Jsont.int
261261- ~enc:cache_read_input_tokens
262262- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
263263- |> Jsont.Object.finish
264264- end
265265-266266- type t = {
267267- subtype : string;
268268- duration_ms : int;
269269- duration_api_ms : int;
270270- is_error : bool;
271271- num_turns : int;
272272- session_id : string;
273273- total_cost_usd : float option;
274274- usage : Usage.t option;
275275- result : string option;
276276- structured_output : Jsont.json option;
277277- unknown : Unknown.t;
278278- }
279279-280280- let create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
281281- ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
282282- {
283283- subtype;
284284- duration_ms;
285285- duration_api_ms;
286286- is_error;
287287- num_turns;
288288- session_id;
289289- total_cost_usd;
290290- usage;
291291- result;
292292- structured_output;
293293- unknown = Unknown.empty;
294294- }
295295-296296- let make subtype duration_ms duration_api_ms is_error num_turns session_id
297297- total_cost_usd usage result structured_output unknown =
298298- {
299299- subtype;
300300- duration_ms;
301301- duration_api_ms;
302302- is_error;
303303- num_turns;
304304- session_id;
305305- total_cost_usd;
306306- usage;
307307- result;
308308- structured_output;
309309- unknown;
310310- }
311311-312312- let subtype t = t.subtype
313313- let duration_ms t = t.duration_ms
314314- let duration_api_ms t = t.duration_api_ms
315315- let is_error t = t.is_error
316316- let num_turns t = t.num_turns
317317- let session_id t = t.session_id
318318- let total_cost_usd t = t.total_cost_usd
319319- let usage t = t.usage
320320- let result t = t.result
321321- let structured_output t = t.structured_output
322322- let unknown t = t.unknown
323323-324324- let jsont : t Jsont.t =
325325- Jsont.Object.map ~kind:"Result" make
326326- |> Jsont.Object.mem "subtype" Jsont.string ~enc:subtype
327327- |> Jsont.Object.mem "duration_ms" Jsont.int ~enc:duration_ms
328328- |> Jsont.Object.mem "duration_api_ms" Jsont.int ~enc:duration_api_ms
329329- |> Jsont.Object.mem "is_error" Jsont.bool ~enc:is_error
330330- |> Jsont.Object.mem "num_turns" Jsont.int ~enc:num_turns
331331- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
332332- |> Jsont.Object.opt_mem "total_cost_usd" Jsont.number ~enc:total_cost_usd
333333- |> Jsont.Object.opt_mem "usage" Usage.jsont ~enc:usage
334334- |> Jsont.Object.opt_mem "result" Jsont.string ~enc:result
335335- |> Jsont.Object.opt_mem "structured_output" Jsont.json
336336- ~enc:structured_output
337337- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
338338- |> Jsont.Object.finish
339339-end
340340-341341-type t =
342342- | User of User.t
343343- | Assistant of Assistant.t
344344- | System of System.t
345345- | Result of Result.t
346346-347347-(* Jsont codec for the main Message variant type.
348348- Uses case_mem for discriminated union based on "type" field. *)
349349-let jsont : t Jsont.t =
350350- let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
351351- let case_user = case_map "user" User.incoming_jsont (fun v -> User v) in
352352- let case_assistant =
353353- case_map "assistant" Assistant.incoming_jsont (fun v -> Assistant v)
354354- in
355355- let case_system = case_map "system" System.jsont (fun v -> System v) in
356356- let case_result = case_map "result" Result.jsont (fun v -> Result v) in
357357- let enc_case = function
358358- | User v -> Jsont.Object.Case.value case_user v
359359- | Assistant v -> Jsont.Object.Case.value case_assistant v
360360- | System v -> Jsont.Object.Case.value case_system v
361361- | Result v -> Jsont.Object.Case.value case_result v
362362- in
363363- let cases =
364364- Jsont.Object.Case.
365365- [
366366- make case_user; make case_assistant; make case_system; make case_result;
367367- ]
368368- in
369369- Jsont.Object.map ~kind:"Message" Fun.id
370370- |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
371371- ~tag_to_string:Fun.id ~tag_compare:String.compare
372372- |> Jsont.Object.finish
-352
proto/message.ml.bak
···11-module User = struct
22- type content = String of string | Blocks of Content_block.t list
33- type t = { content : content; unknown : Unknown.t }
44-55- let create_string s = { content = String s; unknown = Unknown.empty }
66-77- let create_blocks blocks =
88- { content = Blocks blocks; unknown = Unknown.empty }
99-1010- let create_with_tool_result ~tool_use_id ~content ?is_error () =
1111- let tool_result =
1212- Content_block.tool_result ~tool_use_id ~content ?is_error ()
1313- in
1414- { content = Blocks [ tool_result ]; unknown = Unknown.empty }
1515-1616- let make content unknown = { content; unknown }
1717- let content t = t.content
1818- let unknown t = t.unknown
1919-2020- (* Decode content from json value *)
2121- let decode_content json =
2222- match json with
2323- | Jsont.String (s, _) -> String s
2424- | Jsont.Array (items, _) ->
2525- let blocks =
2626- List.map
2727- (fun j ->
2828- match Jsont.Json.decode Content_block.jsont j with
2929- | Ok v -> v
3030- | Error e -> invalid_arg ("Invalid content block: " ^ e))
3131- items
3232- in
3333- Blocks blocks
3434- | _ -> failwith "Content must be string or array"
3535-3636- (* Encode content to json value *)
3737- let encode_content = function
3838- | String s -> Jsont.String (s, Jsont.Meta.none)
3939- | Blocks blocks ->
4040- let jsons =
4141- List.map
4242- (fun b ->
4343- match Jsont.Json.encode Content_block.jsont b with
4444- | Ok json -> json
4545- | Error e -> invalid_arg ("encode_content: " ^ e))
4646- blocks
4747- in
4848- Jsont.Array (jsons, Jsont.Meta.none)
4949-5050- let jsont : t Jsont.t =
5151- Jsont.Object.map ~kind:"User" (fun json_content unknown ->
5252- let content = decode_content json_content in
5353- make content unknown)
5454- |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
5555- encode_content (content t))
5656- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
5757- |> Jsont.Object.finish
5858-5959- (* Jsont codec for parsing incoming user messages from CLI *)
6060- let incoming_jsont : t Jsont.t =
6161- let message_jsont =
6262- Jsont.Object.map ~kind:"UserMessage" (fun json_content ->
6363- let content = decode_content json_content in
6464- { content; unknown = Unknown.empty })
6565- |> Jsont.Object.mem "content" Jsont.json ~enc:(fun t ->
6666- encode_content (content t))
6767- |> Jsont.Object.finish
6868- in
6969- Jsont.Object.map ~kind:"UserEnvelope" Fun.id
7070- |> Jsont.Object.mem "message" message_jsont ~enc:Fun.id
7171- |> Jsont.Object.finish
7272-end
7373-7474-module Assistant = struct
7575- type error =
7676- [ `Authentication_failed
7777- | `Billing_error
7878- | `Rate_limit
7979- | `Invalid_request
8080- | `Server_error
8181- | `Unknown ]
8282-8383- let error_jsont : error Jsont.t =
8484- Jsont.enum
8585- [
8686- ("authentication_failed", `Authentication_failed);
8787- ("billing_error", `Billing_error);
8888- ("rate_limit", `Rate_limit);
8989- ("invalid_request", `Invalid_request);
9090- ("server_error", `Server_error);
9191- ("unknown", `Unknown);
9292- ]
9393-9494- type t = {
9595- content : Content_block.t list;
9696- model : string;
9797- error : error option;
9898- unknown : Unknown.t;
9999- }
100100-101101- let create ~content ~model ?error () =
102102- { content; model; error; unknown = Unknown.empty }
103103-104104- let make content model error unknown = { content; model; error; unknown }
105105- let content t = t.content
106106- let model t = t.model
107107- let error t = t.error
108108- let unknown t = t.unknown
109109-110110- let jsont : t Jsont.t =
111111- Jsont.Object.map ~kind:"Assistant" make
112112- |> Jsont.Object.mem "content" (Jsont.list Content_block.jsont) ~enc:content
113113- |> Jsont.Object.mem "model" Jsont.string ~enc:model
114114- |> Jsont.Object.opt_mem "error" error_jsont ~enc:error
115115- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
116116- |> Jsont.Object.finish
117117-118118- (* Jsont codec for parsing incoming assistant messages from CLI *)
119119- let incoming_jsont : t Jsont.t =
120120- Jsont.Object.map ~kind:"AssistantEnvelope" Fun.id
121121- |> Jsont.Object.mem "message" jsont ~enc:Fun.id
122122- |> Jsont.Object.finish
123123-end
124124-125125-module System = struct
126126- (** System messages as a discriminated union on "subtype" field *)
127127-128128- type init = {
129129- session_id : string option;
130130- model : string option;
131131- cwd : string option;
132132- unknown : Unknown.t;
133133- }
134134-135135- type error = { error : string; unknown : Unknown.t }
136136- type t = Init of init | Error of error
137137-138138- (* Accessors *)
139139- let session_id = function Init i -> i.session_id | _ -> None
140140- let model = function Init i -> i.model | _ -> None
141141- let cwd = function Init i -> i.cwd | _ -> None
142142- let error_msg = function Error e -> Some e.error | _ -> None
143143- let unknown = function Init i -> i.unknown | Error e -> e.unknown
144144-145145- (* Constructors *)
146146- let init ?session_id ?model ?cwd () =
147147- Init { session_id; model; cwd; unknown = Unknown.empty }
148148-149149- let error ~error = Error { error; unknown = Unknown.empty }
150150-151151- (* Individual record codecs *)
152152- let init_jsont : init Jsont.t =
153153- let make session_id model cwd unknown : init =
154154- { session_id; model; cwd; unknown }
155155- in
156156- Jsont.Object.map ~kind:"SystemInit" make
157157- |> Jsont.Object.opt_mem "session_id" Jsont.string ~enc:(fun (r : init) ->
158158- r.session_id)
159159- |> Jsont.Object.opt_mem "model" Jsont.string ~enc:(fun (r : init) ->
160160- r.model)
161161- |> Jsont.Object.opt_mem "cwd" Jsont.string ~enc:(fun (r : init) -> r.cwd)
162162- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : init) ->
163163- r.unknown)
164164- |> Jsont.Object.finish
165165-166166- let error_jsont : error Jsont.t =
167167- let make err unknown : error = { error = err; unknown } in
168168- Jsont.Object.map ~kind:"SystemError" make
169169- |> Jsont.Object.mem "error" Jsont.string ~enc:(fun (r : error) -> r.error)
170170- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun (r : error) ->
171171- r.unknown)
172172- |> Jsont.Object.finish
173173-174174- (* Main codec using case_mem for "subtype" discriminator *)
175175- let jsont : t Jsont.t =
176176- let case_init =
177177- Jsont.Object.Case.map "init" init_jsont ~dec:(fun v -> Init v)
178178- in
179179- let case_error =
180180- Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v)
181181- in
182182- let enc_case = function
183183- | Init v -> Jsont.Object.Case.value case_init v
184184- | Error v -> Jsont.Object.Case.value case_error v
185185- in
186186- let cases = Jsont.Object.Case.[ make case_init; make case_error ] in
187187- Jsont.Object.map ~kind:"System" Fun.id
188188- |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases
189189- ~tag_to_string:Fun.id ~tag_compare:String.compare
190190- |> Jsont.Object.finish
191191-end
192192-193193-module Result = struct
194194- module Usage = struct
195195- type t = {
196196- input_tokens : int option;
197197- output_tokens : int option;
198198- total_tokens : int option;
199199- cache_creation_input_tokens : int option;
200200- cache_read_input_tokens : int option;
201201- unknown : Unknown.t;
202202- }
203203-204204- let make input_tokens output_tokens total_tokens cache_creation_input_tokens
205205- cache_read_input_tokens unknown =
206206- {
207207- input_tokens;
208208- output_tokens;
209209- total_tokens;
210210- cache_creation_input_tokens;
211211- cache_read_input_tokens;
212212- unknown;
213213- }
214214-215215- let create ?input_tokens ?output_tokens ?total_tokens
216216- ?cache_creation_input_tokens ?cache_read_input_tokens () =
217217- {
218218- input_tokens;
219219- output_tokens;
220220- total_tokens;
221221- cache_creation_input_tokens;
222222- cache_read_input_tokens;
223223- unknown = Unknown.empty;
224224- }
225225-226226- let input_tokens t = t.input_tokens
227227- let output_tokens t = t.output_tokens
228228- let total_tokens t = t.total_tokens
229229- let cache_creation_input_tokens t = t.cache_creation_input_tokens
230230- let cache_read_input_tokens t = t.cache_read_input_tokens
231231- let unknown t = t.unknown
232232-233233- let jsont : t Jsont.t =
234234- Jsont.Object.map ~kind:"Usage" make
235235- |> Jsont.Object.opt_mem "input_tokens" Jsont.int ~enc:input_tokens
236236- |> Jsont.Object.opt_mem "output_tokens" Jsont.int ~enc:output_tokens
237237- |> Jsont.Object.opt_mem "total_tokens" Jsont.int ~enc:total_tokens
238238- |> Jsont.Object.opt_mem "cache_creation_input_tokens" Jsont.int
239239- ~enc:cache_creation_input_tokens
240240- |> Jsont.Object.opt_mem "cache_read_input_tokens" Jsont.int
241241- ~enc:cache_read_input_tokens
242242- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
243243- |> Jsont.Object.finish
244244- end
245245-246246- type t = {
247247- subtype : string;
248248- duration_ms : int;
249249- duration_api_ms : int;
250250- is_error : bool;
251251- num_turns : int;
252252- session_id : string;
253253- total_cost_usd : float option;
254254- usage : Usage.t option;
255255- result : string option;
256256- structured_output : Jsont.json option;
257257- unknown : Unknown.t;
258258- }
259259-260260- let create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
261261- ~session_id ?total_cost_usd ?usage ?result ?structured_output () =
262262- {
263263- subtype;
264264- duration_ms;
265265- duration_api_ms;
266266- is_error;
267267- num_turns;
268268- session_id;
269269- total_cost_usd;
270270- usage;
271271- result;
272272- structured_output;
273273- unknown = Unknown.empty;
274274- }
275275-276276- let make subtype duration_ms duration_api_ms is_error num_turns session_id
277277- total_cost_usd usage result structured_output unknown =
278278- {
279279- subtype;
280280- duration_ms;
281281- duration_api_ms;
282282- is_error;
283283- num_turns;
284284- session_id;
285285- total_cost_usd;
286286- usage;
287287- result;
288288- structured_output;
289289- unknown;
290290- }
291291-292292- let subtype t = t.subtype
293293- let duration_ms t = t.duration_ms
294294- let duration_api_ms t = t.duration_api_ms
295295- let is_error t = t.is_error
296296- let num_turns t = t.num_turns
297297- let session_id t = t.session_id
298298- let total_cost_usd t = t.total_cost_usd
299299- let usage t = t.usage
300300- let result t = t.result
301301- let structured_output t = t.structured_output
302302- let unknown t = t.unknown
303303-304304- let jsont : t Jsont.t =
305305- Jsont.Object.map ~kind:"Result" make
306306- |> Jsont.Object.mem "subtype" Jsont.string ~enc:subtype
307307- |> Jsont.Object.mem "duration_ms" Jsont.int ~enc:duration_ms
308308- |> Jsont.Object.mem "duration_api_ms" Jsont.int ~enc:duration_api_ms
309309- |> Jsont.Object.mem "is_error" Jsont.bool ~enc:is_error
310310- |> Jsont.Object.mem "num_turns" Jsont.int ~enc:num_turns
311311- |> Jsont.Object.mem "session_id" Jsont.string ~enc:session_id
312312- |> Jsont.Object.opt_mem "total_cost_usd" Jsont.number ~enc:total_cost_usd
313313- |> Jsont.Object.opt_mem "usage" Usage.jsont ~enc:usage
314314- |> Jsont.Object.opt_mem "result" Jsont.string ~enc:result
315315- |> Jsont.Object.opt_mem "structured_output" Jsont.json
316316- ~enc:structured_output
317317- |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
318318- |> Jsont.Object.finish
319319-end
320320-321321-type t =
322322- | User of User.t
323323- | Assistant of Assistant.t
324324- | System of System.t
325325- | Result of Result.t
326326-327327-(* Jsont codec for the main Message variant type.
328328- Uses case_mem for discriminated union based on "type" field. *)
329329-let jsont : t Jsont.t =
330330- let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
331331- let case_user = case_map "user" User.incoming_jsont (fun v -> User v) in
332332- let case_assistant =
333333- case_map "assistant" Assistant.incoming_jsont (fun v -> Assistant v)
334334- in
335335- let case_system = case_map "system" System.jsont (fun v -> System v) in
336336- let case_result = case_map "result" Result.jsont (fun v -> Result v) in
337337- let enc_case = function
338338- | User v -> Jsont.Object.Case.value case_user v
339339- | Assistant v -> Jsont.Object.Case.value case_assistant v
340340- | System v -> Jsont.Object.Case.value case_system v
341341- | Result v -> Jsont.Object.Case.value case_result v
342342- in
343343- let cases =
344344- Jsont.Object.Case.
345345- [
346346- make case_user; make case_assistant; make case_system; make case_result;
347347- ]
348348- in
349349- Jsont.Object.map ~kind:"Message" Fun.id
350350- |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
351351- ~tag_to_string:Fun.id ~tag_compare:String.compare
352352- |> Jsont.Object.finish
-276
proto/message.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Messages exchanged with Claude wire format.
77-88- This module defines the wire format types for messages that can be sent to
99- and received from Claude, including user input, assistant responses, system
1010- messages, and result metadata. *)
1111-1212-(** {1 User Messages} *)
1313-1414-module User : sig
1515- (** Messages sent by the user. *)
1616-1717- (** The content of a user message. *)
1818- type content =
1919- | String of string (** Simple text message *)
2020- | Blocks of Content_block.t list
2121- (** Complex message with multiple content blocks *)
2222-2323- type t
2424- (** The type of user messages. *)
2525-2626- val jsont : t Jsont.t
2727- (** [jsont] is the Jsont codec for user messages. *)
2828-2929- val incoming_jsont : t Jsont.t
3030- (** [incoming_jsont] is the codec for parsing incoming user messages from CLI.
3131- This parses the envelope format with "message" wrapper. *)
3232-3333- val outgoing_jsont : t Jsont.t
3434- (** [outgoing_jsont] is the codec for encoding outgoing user messages to CLI.
3535- This produces the envelope format with "message" wrapper containing "role"
3636- and "content" fields. *)
3737-3838- val create_string : string -> t
3939- (** [create_string s] creates a user message with simple text content. *)
4040-4141- val create_blocks : Content_block.t list -> t
4242- (** [create_blocks blocks] creates a user message with content blocks. *)
4343-4444- val create_with_tool_result :
4545- tool_use_id:string -> content:Jsont.json -> ?is_error:bool -> unit -> t
4646- (** [create_with_tool_result ~tool_use_id ~content ?is_error ()] creates a
4747- user message containing a tool result. Content can be a string or array.
4848- *)
4949-5050- val content : t -> content
5151- (** [content t] returns the content of the user message. *)
5252-5353- val unknown : t -> Unknown.t
5454- (** [unknown t] returns the unknown fields preserved from JSON. *)
5555-end
5656-5757-(** {1 Assistant Messages} *)
5858-5959-module Assistant : sig
6060- (** Messages from Claude assistant. *)
6161-6262- type error =
6363- [ `Authentication_failed (** Authentication with Claude API failed *)
6464- | `Billing_error (** Billing or account issue *)
6565- | `Rate_limit (** Rate limit exceeded *)
6666- | `Invalid_request (** Request was invalid *)
6767- | `Server_error (** Internal server error *)
6868- | `Unknown (** Unknown error type *) ]
6969- (** The type of assistant message errors based on Python SDK error types. *)
7070-7171- type t
7272- (** The type of assistant messages. *)
7373-7474- val jsont : t Jsont.t
7575- (** [jsont] is the Jsont codec for assistant messages. *)
7676-7777- val incoming_jsont : t Jsont.t
7878- (** [incoming_jsont] is the codec for parsing incoming assistant messages from
7979- CLI. This parses the envelope format with "message" wrapper. *)
8080-8181- val create :
8282- content:Content_block.t list -> model:string -> ?error:error -> unit -> t
8383- (** [create ~content ~model ?error ()] creates an assistant message.
8484- @param content List of content blocks in the response
8585- @param model The model identifier used for the response
8686- @param error Optional error that occurred during message generation *)
8787-8888- val content : t -> Content_block.t list
8989- (** [content t] returns the content blocks of the assistant message. *)
9090-9191- val model : t -> string
9292- (** [model t] returns the model identifier. *)
9393-9494- val error : t -> error option
9595- (** [error t] returns the optional error that occurred during message
9696- generation. *)
9797-9898- val unknown : t -> Unknown.t
9999- (** [unknown t] returns the unknown fields preserved from JSON. *)
100100-end
101101-102102-(** {1 System Messages} *)
103103-104104-module System : sig
105105- (** System control and status messages.
106106-107107- System messages use a discriminated union on the "subtype" field:
108108- - "init": Session initialization with session_id, model, cwd
109109- - "error": Error messages with error string *)
110110-111111- type init = {
112112- session_id : string option;
113113- model : string option;
114114- cwd : string option;
115115- unknown : Unknown.t;
116116- }
117117- (** Init message fields. *)
118118-119119- type error = { error : string; unknown : Unknown.t }
120120- (** Error message fields. *)
121121-122122- type t = Init of init | Error of error
123123-124124- val jsont : t Jsont.t
125125- (** [jsont] is the Jsont codec for system messages. *)
126126-127127- (** {2 Constructors} *)
128128-129129- val init : ?session_id:string -> ?model:string -> ?cwd:string -> unit -> t
130130- (** [init ?session_id ?model ?cwd ()] creates an init message. *)
131131-132132- val error : error:string -> t
133133- (** [error ~error] creates an error message. *)
134134-135135- (** {2 Accessors} *)
136136-137137- val session_id : t -> string option
138138- (** [session_id t] returns session_id from Init, None otherwise. *)
139139-140140- val model : t -> string option
141141- (** [model t] returns model from Init, None otherwise. *)
142142-143143- val cwd : t -> string option
144144- (** [cwd t] returns cwd from Init, None otherwise. *)
145145-146146- val error_msg : t -> string option
147147- (** [error_msg t] returns error from Error, None otherwise. *)
148148-149149- val unknown : t -> Unknown.t
150150- (** [unknown t] returns the unknown fields. *)
151151-end
152152-153153-(** {1 Result Messages} *)
154154-155155-module Result : sig
156156- (** Final result messages with metadata about the conversation. *)
157157-158158- module Usage : sig
159159- (** Usage statistics for API calls. *)
160160-161161- type t
162162- (** Type for usage statistics. *)
163163-164164- val jsont : t Jsont.t
165165- (** [jsont] is the Jsont codec for usage statistics. *)
166166-167167- val create :
168168- ?input_tokens:int ->
169169- ?output_tokens:int ->
170170- ?total_tokens:int ->
171171- ?cache_creation_input_tokens:int ->
172172- ?cache_read_input_tokens:int ->
173173- unit ->
174174- t
175175- (** [create ?input_tokens ?output_tokens ?total_tokens
176176- ?cache_creation_input_tokens ?cache_read_input_tokens ()] creates usage
177177- statistics. *)
178178-179179- val input_tokens : t -> int option
180180- (** [input_tokens t] returns the number of input tokens used. *)
181181-182182- val output_tokens : t -> int option
183183- (** [output_tokens t] returns the number of output tokens generated. *)
184184-185185- val total_tokens : t -> int option
186186- (** [total_tokens t] returns the total number of tokens. *)
187187-188188- val cache_creation_input_tokens : t -> int option
189189- (** [cache_creation_input_tokens t] returns cache creation input tokens. *)
190190-191191- val cache_read_input_tokens : t -> int option
192192- (** [cache_read_input_tokens t] returns cache read input tokens. *)
193193-194194- val unknown : t -> Unknown.t
195195- (** [unknown t] returns the unknown fields preserved from JSON. *)
196196- end
197197-198198- type t
199199- (** The type of result messages. *)
200200-201201- val jsont : t Jsont.t
202202- (** [jsont] is the Jsont codec for result messages. *)
203203-204204- val create :
205205- subtype:string ->
206206- duration_ms:int ->
207207- duration_api_ms:int ->
208208- is_error:bool ->
209209- num_turns:int ->
210210- session_id:string ->
211211- ?total_cost_usd:float ->
212212- ?usage:Usage.t ->
213213- ?result:string ->
214214- ?structured_output:Jsont.json ->
215215- unit ->
216216- t
217217- (** [create ~subtype ~duration_ms ~duration_api_ms ~is_error ~num_turns
218218- ~session_id ?total_cost_usd ?usage ?result ?structured_output ()] creates
219219- a result message.
220220- @param subtype The subtype of the result
221221- @param duration_ms Total duration in milliseconds
222222- @param duration_api_ms API duration in milliseconds
223223- @param is_error Whether the result represents an error
224224- @param num_turns Number of conversation turns
225225- @param session_id Unique session identifier
226226- @param total_cost_usd Optional total cost in USD
227227- @param usage Optional usage statistics
228228- @param result Optional result string
229229- @param structured_output Optional structured JSON output from Claude *)
230230-231231- val subtype : t -> string
232232- (** [subtype t] returns the subtype of the result. *)
233233-234234- val duration_ms : t -> int
235235- (** [duration_ms t] returns the total duration in milliseconds. *)
236236-237237- val duration_api_ms : t -> int
238238- (** [duration_api_ms t] returns the API duration in milliseconds. *)
239239-240240- val is_error : t -> bool
241241- (** [is_error t] returns whether this result represents an error. *)
242242-243243- val num_turns : t -> int
244244- (** [num_turns t] returns the number of conversation turns. *)
245245-246246- val session_id : t -> string
247247- (** [session_id t] returns the session identifier. *)
248248-249249- val total_cost_usd : t -> float option
250250- (** [total_cost_usd t] returns the optional total cost in USD. *)
251251-252252- val usage : t -> Usage.t option
253253- (** [usage t] returns the optional usage statistics. *)
254254-255255- val result : t -> string option
256256- (** [result t] returns the optional result string. *)
257257-258258- val structured_output : t -> Jsont.json option
259259- (** [structured_output t] returns the optional structured JSON output. *)
260260-261261- val unknown : t -> Unknown.t
262262- (** [unknown t] returns the unknown fields preserved from JSON. *)
263263-end
264264-265265-(** {1 Message Union Type} *)
266266-267267-type t =
268268- | User of User.t
269269- | Assistant of Assistant.t
270270- | System of System.t
271271- | Result of Result.t
272272- (** The type of messages, which can be user, assistant, system, or result.
273273- *)
274274-275275-val jsont : t Jsont.t
276276-(** [jsont] is the Jsont codec for messages. *)
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Claude AI model identifiers for protocol encoding.
77-88- This module provides type-safe model identifiers with JSON encoding/decoding
99- support via Jsont. Use polymorphic variants for known models with a custom
1010- escape hatch for future or unknown models. *)
1111-1212-type t =
1313- [ `Sonnet_4_5 (** claude-sonnet-4-5 - Most recent Sonnet model *)
1414- | `Sonnet_4 (** claude-sonnet-4 - Sonnet 4 model *)
1515- | `Sonnet_3_5 (** claude-sonnet-3-5 - Sonnet 3.5 model *)
1616- | `Opus_4_5 (** claude-opus-4-5 - Most recent Opus model *)
1717- | `Opus_4_1 (** claude-opus-4-1 - Opus 4.1 model *)
1818- | `Opus_4 (** claude-opus-4 - Opus 4 model for complex tasks *)
1919- | `Haiku_4 (** claude-haiku-4 - Fast, cost-effective Haiku model *)
2020- | `Custom of string (** Custom model string for future/unknown models *) ]
2121-(** The type of Claude models. *)
2222-2323-val to_string : t -> string
2424-(** [to_string t] converts a model to its string representation.
2525-2626- Examples:
2727- - [`Sonnet_4_5] becomes "claude-sonnet-4-5"
2828- - [`Opus_4_5] becomes "claude-opus-4-5"
2929- - [`Opus_4] becomes "claude-opus-4"
3030- - [`Custom "my-model"] becomes "my-model" *)
3131-3232-val of_string : string -> t
3333-(** [of_string s] parses a model string into a typed model.
3434-3535- Known model strings are converted to their typed variants. Unknown strings
3636- become [`Custom s].
3737-3838- Examples:
3939- - "claude-sonnet-4-5" becomes [`Sonnet_4_5]
4040- - "future-model" becomes [`Custom "future-model"] *)
4141-4242-val jsont : t Jsont.t
4343-(** [jsont] is the Jsont codec for model identifiers.
4444-4545- This codec maps between the typed model representation and JSON strings. It
4646- uses [of_string] for decoding and [to_string] for encoding. *)
-191
proto/options.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Wire format for Claude configuration options. *)
77-88-(** Setting sources *)
99-type setting_source = User | Project | Local
1010-1111-let setting_source_jsont : setting_source Jsont.t =
1212- Jsont.enum [ ("user", User); ("project", Project); ("local", Local) ]
1313-1414-type t = {
1515- allowed_tools : string list;
1616- disallowed_tools : string list;
1717- max_thinking_tokens : int option;
1818- system_prompt : string option;
1919- append_system_prompt : string option;
2020- permission_mode : Permissions.Mode.t option;
2121- model : Model.t option;
2222- continue_conversation : bool;
2323- resume : string option;
2424- max_turns : int option;
2525- permission_prompt_tool_name : string option;
2626- settings : string option;
2727- add_dirs : string list;
2828- max_budget_usd : float option;
2929- fallback_model : Model.t option;
3030- setting_sources : setting_source list option;
3131- max_buffer_size : int option;
3232- user : string option;
3333- output_format : Structured_output.t option;
3434- unknown : Unknown.t;
3535-}
3636-(** Configuration type *)
3737-3838-let empty =
3939- {
4040- allowed_tools = [];
4141- disallowed_tools = [];
4242- max_thinking_tokens = None;
4343- system_prompt = None;
4444- append_system_prompt = None;
4545- permission_mode = None;
4646- model = None;
4747- continue_conversation = false;
4848- resume = None;
4949- max_turns = None;
5050- permission_prompt_tool_name = None;
5151- settings = None;
5252- add_dirs = [];
5353- max_budget_usd = None;
5454- fallback_model = None;
5555- setting_sources = None;
5656- max_buffer_size = None;
5757- user = None;
5858- output_format = None;
5959- unknown = Unknown.empty;
6060- }
6161-6262-(** Accessor functions *)
6363-let allowed_tools t = t.allowed_tools
6464-6565-let disallowed_tools t = t.disallowed_tools
6666-let max_thinking_tokens t = t.max_thinking_tokens
6767-let system_prompt t = t.system_prompt
6868-let append_system_prompt t = t.append_system_prompt
6969-let permission_mode t = t.permission_mode
7070-let model t = t.model
7171-let continue_conversation t = t.continue_conversation
7272-let resume t = t.resume
7373-let max_turns t = t.max_turns
7474-let permission_prompt_tool_name t = t.permission_prompt_tool_name
7575-let settings t = t.settings
7676-let add_dirs t = t.add_dirs
7777-let max_budget_usd t = t.max_budget_usd
7878-let fallback_model t = t.fallback_model
7979-let setting_sources t = t.setting_sources
8080-let max_buffer_size t = t.max_buffer_size
8181-let user t = t.user
8282-let output_format t = t.output_format
8383-let unknown t = t.unknown
8484-8585-(** Builder functions *)
8686-let with_allowed_tools allowed_tools t = { t with allowed_tools }
8787-8888-let with_disallowed_tools disallowed_tools t = { t with disallowed_tools }
8989-9090-let with_max_thinking_tokens max_thinking_tokens t =
9191- { t with max_thinking_tokens = Some max_thinking_tokens }
9292-9393-let with_system_prompt system_prompt t =
9494- { t with system_prompt = Some system_prompt }
9595-9696-let with_append_system_prompt append_system_prompt t =
9797- { t with append_system_prompt = Some append_system_prompt }
9898-9999-let with_permission_mode permission_mode t =
100100- { t with permission_mode = Some permission_mode }
101101-102102-let with_model model t = { t with model = Some model }
103103-104104-let with_continue_conversation continue_conversation t =
105105- { t with continue_conversation }
106106-107107-let with_resume resume t = { t with resume = Some resume }
108108-let with_max_turns max_turns t = { t with max_turns = Some max_turns }
109109-110110-let with_permission_prompt_tool_name permission_prompt_tool_name t =
111111- { t with permission_prompt_tool_name = Some permission_prompt_tool_name }
112112-113113-let with_settings settings t = { t with settings = Some settings }
114114-let with_add_dirs add_dirs t = { t with add_dirs }
115115-116116-let with_max_budget_usd max_budget_usd t =
117117- { t with max_budget_usd = Some max_budget_usd }
118118-119119-let with_fallback_model fallback_model t =
120120- { t with fallback_model = Some fallback_model }
121121-122122-let with_setting_sources setting_sources t =
123123- { t with setting_sources = Some setting_sources }
124124-125125-let with_max_buffer_size max_buffer_size t =
126126- { t with max_buffer_size = Some max_buffer_size }
127127-128128-let with_user user t = { t with user = Some user }
129129-130130-let with_output_format output_format t =
131131- { t with output_format = Some output_format }
132132-133133-(** JSON codec *)
134134-let jsont : t Jsont.t =
135135- let make allowed_tools disallowed_tools max_thinking_tokens system_prompt
136136- append_system_prompt permission_mode model continue_conversation resume
137137- max_turns permission_prompt_tool_name settings add_dirs max_budget_usd
138138- fallback_model setting_sources max_buffer_size user output_format unknown
139139- =
140140- {
141141- allowed_tools;
142142- disallowed_tools;
143143- max_thinking_tokens;
144144- system_prompt;
145145- append_system_prompt;
146146- permission_mode;
147147- model;
148148- continue_conversation;
149149- resume;
150150- max_turns;
151151- permission_prompt_tool_name;
152152- settings;
153153- add_dirs;
154154- max_budget_usd;
155155- fallback_model;
156156- setting_sources;
157157- max_buffer_size;
158158- user;
159159- output_format;
160160- unknown;
161161- }
162162- in
163163- Jsont.Object.(
164164- map ~kind:"Options" make
165165- |> mem "allowedTools" (Jsont.list Jsont.string) ~enc:allowed_tools
166166- ~dec_absent:[]
167167- |> mem "disallowedTools" (Jsont.list Jsont.string) ~enc:disallowed_tools
168168- ~dec_absent:[]
169169- |> opt_mem "maxThinkingTokens" Jsont.int ~enc:max_thinking_tokens
170170- |> opt_mem "systemPrompt" Jsont.string ~enc:system_prompt
171171- |> opt_mem "appendSystemPrompt" Jsont.string ~enc:append_system_prompt
172172- |> opt_mem "permissionMode" Permissions.Mode.jsont ~enc:permission_mode
173173- |> opt_mem "model" Model.jsont ~enc:model
174174- |> mem "continueConversation" Jsont.bool ~enc:continue_conversation
175175- ~dec_absent:false
176176- |> opt_mem "resume" Jsont.string ~enc:resume
177177- |> opt_mem "maxTurns" Jsont.int ~enc:max_turns
178178- |> opt_mem "permissionPromptToolName" Jsont.string
179179- ~enc:permission_prompt_tool_name
180180- |> opt_mem "settings" Jsont.string ~enc:settings
181181- |> mem "addDirs" (Jsont.list Jsont.string) ~enc:add_dirs ~dec_absent:[]
182182- |> opt_mem "maxBudgetUsd" Jsont.number ~enc:max_budget_usd
183183- |> opt_mem "fallbackModel" Model.jsont ~enc:fallback_model
184184- |> opt_mem "settingSources"
185185- (Jsont.list setting_source_jsont)
186186- ~enc:setting_sources
187187- |> opt_mem "maxBufferSize" Jsont.int ~enc:max_buffer_size
188188- |> opt_mem "user" Jsont.string ~enc:user
189189- |> opt_mem "outputFormat" Structured_output.jsont ~enc:output_format
190190- |> keep_unknown Unknown.mems ~enc:unknown
191191- |> finish)
-197
proto/options.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Wire format for Claude configuration options.
77-88- This module provides the protocol-level wire format encoding/decoding for
99- configuration options used in JSON configuration files. It handles JSON
1010- serialization and deserialization with proper field name mappings
1111- (camelCase).
1212-1313- This is the protocol-level module without Eio types or logging. *)
1414-1515-(** {1 Setting Sources} *)
1616-1717-(** The type of setting sources, indicating where configuration was loaded from.
1818-*)
1919-type setting_source =
2020- | User (** User-level settings *)
2121- | Project (** Project-level settings *)
2222- | Local (** Local directory settings *)
2323-2424-(** {1 Configuration Type} *)
2525-2626-type t
2727-(** The type of configuration options.
2828-2929- This represents all configurable options for Claude interactions, encoded in
3030- JSON format. *)
3131-3232-val jsont : t Jsont.t
3333-(** [jsont] is the Jsont codec for configuration options.
3434-3535- Wire format uses camelCase field names:
3636- - allowedTools (array of strings)
3737- - disallowedTools (array of strings)
3838- - maxThinkingTokens (int)
3939- - systemPrompt (string)
4040- - appendSystemPrompt (string)
4141- - permissionMode (string via Permissions.Mode.jsont)
4242- - model (string via Model.jsont)
4343- - continueConversation (bool)
4444- - resume (string)
4545- - maxTurns (int)
4646- - permissionPromptToolName (string)
4747- - settings (string)
4848- - addDirs (array of strings)
4949- - maxBudgetUsd (float)
5050- - fallbackModel (string via Model.jsont)
5151- - settingSources (array of "user", "project", "local")
5252- - maxBufferSize (int)
5353- - user (string)
5454- - outputFormat (object via Structured_output.jsont)
5555-5656- Unknown fields are preserved for forward compatibility. *)
5757-5858-val empty : t
5959-(** [empty] is an empty configuration with all fields set to their default
6060- values.
6161-6262- Default values:
6363- - Lists default to empty
6464- - [maxThinkingTokens] defaults to 8000
6565- - [continueConversation] defaults to false
6666- - All optional fields default to [None] *)
6767-6868-(** {1 Accessor Functions} *)
6969-7070-val allowed_tools : t -> string list
7171-(** [allowed_tools t] returns the list of allowed tool names. Empty list means
7272- all tools are allowed (unless explicitly disallowed). *)
7373-7474-val disallowed_tools : t -> string list
7575-(** [disallowed_tools t] returns the list of disallowed tool names. *)
7676-7777-val max_thinking_tokens : t -> int option
7878-(** [max_thinking_tokens t] returns the maximum number of tokens Claude can use
7979- for internal thinking. *)
8080-8181-val system_prompt : t -> string option
8282-(** [system_prompt t] returns the system prompt to use for Claude. *)
8383-8484-val append_system_prompt : t -> string option
8585-(** [append_system_prompt t] returns additional text to append to the system
8686- prompt. *)
8787-8888-val permission_mode : t -> Permissions.Mode.t option
8989-(** [permission_mode t] returns the permission mode controlling how tool
9090- invocations are authorized. *)
9191-9292-val model : t -> Model.t option
9393-(** [model t] returns the Claude model to use for interactions. *)
9494-9595-val continue_conversation : t -> bool
9696-(** [continue_conversation t] returns whether to continue from a previous
9797- conversation. *)
9898-9999-val resume : t -> string option
100100-(** [resume t] returns the session ID to resume from. *)
101101-102102-val max_turns : t -> int option
103103-(** [max_turns t] returns the maximum number of conversation turns to allow. *)
104104-105105-val permission_prompt_tool_name : t -> string option
106106-(** [permission_prompt_tool_name t] returns the tool name to use for permission
107107- prompts. *)
108108-109109-val settings : t -> string option
110110-(** [settings t] returns the path to the settings file. *)
111111-112112-val add_dirs : t -> string list
113113-(** [add_dirs t] returns additional directories to include in the context. *)
114114-115115-val max_budget_usd : t -> float option
116116-(** [max_budget_usd t] returns the maximum budget in USD for API calls. *)
117117-118118-val fallback_model : t -> Model.t option
119119-(** [fallback_model t] returns the fallback model to use if the primary model
120120- fails. *)
121121-122122-val setting_sources : t -> setting_source list option
123123-(** [setting_sources t] returns the list of setting sources to load from. *)
124124-125125-val max_buffer_size : t -> int option
126126-(** [max_buffer_size t] returns the maximum buffer size for I/O operations. *)
127127-128128-val user : t -> string option
129129-(** [user t] returns the user identifier for the session. *)
130130-131131-val output_format : t -> Structured_output.t option
132132-(** [output_format t] returns the structured output format configuration. *)
133133-134134-val unknown : t -> Unknown.t
135135-(** [unknown t] returns the unknown fields preserved from JSON parsing. *)
136136-137137-(** {1 Builder Functions} *)
138138-139139-val with_allowed_tools : string list -> t -> t
140140-(** [with_allowed_tools tools t] sets the allowed tools. *)
141141-142142-val with_disallowed_tools : string list -> t -> t
143143-(** [with_disallowed_tools tools t] sets the disallowed tools. *)
144144-145145-val with_max_thinking_tokens : int -> t -> t
146146-(** [with_max_thinking_tokens tokens t] sets the maximum thinking tokens. *)
147147-148148-val with_system_prompt : string -> t -> t
149149-(** [with_system_prompt prompt t] sets the system prompt. *)
150150-151151-val with_append_system_prompt : string -> t -> t
152152-(** [with_append_system_prompt prompt t] sets the text to append to the system
153153- prompt. *)
154154-155155-val with_permission_mode : Permissions.Mode.t -> t -> t
156156-(** [with_permission_mode mode t] sets the permission mode. *)
157157-158158-val with_model : Model.t -> t -> t
159159-(** [with_model model t] sets the Claude model. *)
160160-161161-val with_continue_conversation : bool -> t -> t
162162-(** [with_continue_conversation continue t] sets whether to continue
163163- conversation. *)
164164-165165-val with_resume : string -> t -> t
166166-(** [with_resume session_id t] sets the session ID to resume from. *)
167167-168168-val with_max_turns : int -> t -> t
169169-(** [with_max_turns turns t] sets the maximum number of turns. *)
170170-171171-val with_permission_prompt_tool_name : string -> t -> t
172172-(** [with_permission_prompt_tool_name tool t] sets the permission prompt tool
173173- name. *)
174174-175175-val with_settings : string -> t -> t
176176-(** [with_settings path t] sets the settings file path. *)
177177-178178-val with_add_dirs : string list -> t -> t
179179-(** [with_add_dirs dirs t] sets the additional directories. *)
180180-181181-val with_max_budget_usd : float -> t -> t
182182-(** [with_max_budget_usd budget t] sets the maximum budget. *)
183183-184184-val with_fallback_model : Model.t -> t -> t
185185-(** [with_fallback_model model t] sets the fallback model. *)
186186-187187-val with_setting_sources : setting_source list -> t -> t
188188-(** [with_setting_sources sources t] sets the setting sources. *)
189189-190190-val with_max_buffer_size : int -> t -> t
191191-(** [with_max_buffer_size size t] sets the maximum buffer size. *)
192192-193193-val with_user : string -> t -> t
194194-(** [with_user user t] sets the user identifier. *)
195195-196196-val with_output_format : Structured_output.t -> t -> t
197197-(** [with_output_format format t] sets the structured output format. *)
-82
proto/outgoing.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Outgoing messages to Claude CLI.
77-88- This uses the Message.jsont for conversation messages and Control envelope
99- codecs for control messages. The top-level discriminator is the "type"
1010- field. *)
1111-1212-type t =
1313- | Message of Message.t
1414- | Control_request of Control.request_envelope
1515- | Control_response of Control.response_envelope
1616-1717-let jsont : t Jsont.t =
1818- (* Message types use "user", "assistant", "system", "result" as type values.
1919- Control uses "control_request" and "control_response".
2020-2121- We use case_mem for all types. For Message, we use Message.jsont which
2222- already handles the inner "type" discrimination. *)
2323- let case_control_request =
2424- Jsont.Object.Case.map "control_request" Control.request_envelope_jsont
2525- ~dec:(fun v -> Control_request v)
2626- in
2727- let case_control_response =
2828- Jsont.Object.Case.map "control_response" Control.response_envelope_jsont
2929- ~dec:(fun v -> Control_response v)
3030- in
3131- (* For messages, we need to handle all four message types *)
3232- let case_user =
3333- Jsont.Object.Case.map "user" Message.User.outgoing_jsont ~dec:(fun v ->
3434- Message (Message.User v))
3535- in
3636- let case_assistant =
3737- Jsont.Object.Case.map "assistant" Message.Assistant.jsont ~dec:(fun v ->
3838- Message (Message.Assistant v))
3939- in
4040- let case_system =
4141- Jsont.Object.Case.map "system" Message.System.jsont ~dec:(fun v ->
4242- Message (Message.System v))
4343- in
4444- let case_result =
4545- Jsont.Object.Case.map "result" Message.Result.jsont ~dec:(fun v ->
4646- Message (Message.Result v))
4747- in
4848- let enc_case = function
4949- | Control_request v -> Jsont.Object.Case.value case_control_request v
5050- | Control_response v -> Jsont.Object.Case.value case_control_response v
5151- | Message msg -> (
5252- match msg with
5353- | Message.User u -> Jsont.Object.Case.value case_user u
5454- | Message.Assistant a -> Jsont.Object.Case.value case_assistant a
5555- | Message.System s -> Jsont.Object.Case.value case_system s
5656- | Message.Result r -> Jsont.Object.Case.value case_result r)
5757- in
5858- let cases =
5959- Jsont.Object.Case.
6060- [
6161- make case_control_request;
6262- make case_control_response;
6363- make case_user;
6464- make case_assistant;
6565- make case_system;
6666- make case_result;
6767- ]
6868- in
6969- Jsont.Object.map ~kind:"Outgoing" Fun.id
7070- |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
7171- ~tag_to_string:Fun.id ~tag_compare:String.compare
7272- |> Jsont.Object.finish
7373-7474-let to_json t =
7575- match Jsont.Json.encode jsont t with
7676- | Ok json -> json
7777- | Error e -> invalid_arg ("to_json: " ^ e)
7878-7979-let of_json json =
8080- match Jsont.Json.decode jsont json with
8181- | Ok v -> v
8282- | Error e -> invalid_arg ("of_json: " ^ e)
-24
proto/outgoing.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Outgoing messages to the Claude CLI.
77-88- This module provides encoding for all message types that can be sent to the
99- Claude CLI. *)
1010-1111-type t =
1212- | Message of Message.t
1313- | Control_request of Control.request_envelope
1414- | Control_response of Control.response_envelope
1515-1616-val jsont : t Jsont.t
1717-(** Codec for outgoing messages. *)
1818-1919-val to_json : t -> Jsont.json
2020-(** [to_json t] converts an outgoing message to JSON. *)
2121-2222-val of_json : Jsont.json -> t
2323-(** [of_json json] parses an outgoing message from JSON.
2424- @raise Invalid_argument if parsing fails. *)
-248
proto/permissions.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Permission system wire format for Claude tool invocations.
77-88- This module provides the wire format encoding/decoding for permission types
99- used in the Claude protocol. It handles JSON serialization and
1010- deserialization with proper field name mappings. *)
1111-1212-(** Permission modes *)
1313-module Mode = struct
1414- type t = Default | Accept_edits | Plan | Bypass_permissions
1515-1616- let to_string = function
1717- | Default -> "default"
1818- | Accept_edits -> "acceptEdits"
1919- | Plan -> "plan"
2020- | Bypass_permissions -> "bypassPermissions"
2121-2222- let of_string = function
2323- | "default" -> Default
2424- | "acceptEdits" -> Accept_edits
2525- | "plan" -> Plan
2626- | "bypassPermissions" -> Bypass_permissions
2727- | s ->
2828- raise
2929- (Invalid_argument (Printf.sprintf "Mode.of_string: unknown mode %s" s))
3030-3131- let jsont : t Jsont.t =
3232- Jsont.enum
3333- [
3434- ("default", Default);
3535- ("acceptEdits", Accept_edits);
3636- ("plan", Plan);
3737- ("bypassPermissions", Bypass_permissions);
3838- ]
3939-end
4040-4141-(** Permission behaviors *)
4242-module Behavior = struct
4343- type t = Allow | Deny | Ask
4444-4545- let to_string = function Allow -> "allow" | Deny -> "deny" | Ask -> "ask"
4646-4747- let of_string = function
4848- | "allow" -> Allow
4949- | "deny" -> Deny
5050- | "ask" -> Ask
5151- | s ->
5252- raise
5353- (Invalid_argument
5454- (Printf.sprintf "Behavior.of_string: unknown behavior %s" s))
5555-5656- let jsont : t Jsont.t =
5757- Jsont.enum [ ("allow", Allow); ("deny", Deny); ("ask", Ask) ]
5858-end
5959-6060-(** Permission rules *)
6161-module Rule = struct
6262- type t = {
6363- tool_name : string;
6464- rule_content : string option;
6565- unknown : Unknown.t;
6666- }
6767-6868- let create ~tool_name ?rule_content ?(unknown = Unknown.empty) () =
6969- { tool_name; rule_content; unknown }
7070-7171- let tool_name t = t.tool_name
7272- let rule_content t = t.rule_content
7373- let unknown t = t.unknown
7474-7575- let jsont : t Jsont.t =
7676- let make tool_name rule_content unknown =
7777- { tool_name; rule_content; unknown }
7878- in
7979- Jsont.Object.map ~kind:"Rule" make
8080- |> Jsont.Object.mem "toolName" Jsont.string ~enc:tool_name
8181- |> Jsont.Object.opt_mem "ruleContent" Jsont.string ~enc:rule_content
8282- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
8383- |> Jsont.Object.finish
8484-end
8585-8686-(** Permission updates *)
8787-module Update = struct
8888- type destination =
8989- | User_settings
9090- | Project_settings
9191- | Local_settings
9292- | Session
9393-9494- let destination_jsont : destination Jsont.t =
9595- Jsont.enum
9696- [
9797- ("userSettings", User_settings);
9898- ("projectSettings", Project_settings);
9999- ("localSettings", Local_settings);
100100- ("session", Session);
101101- ]
102102-103103- type update_type =
104104- | Add_rules
105105- | Replace_rules
106106- | Remove_rules
107107- | Set_mode
108108- | Add_directories
109109- | Remove_directories
110110-111111- let update_type_jsont : update_type Jsont.t =
112112- Jsont.enum
113113- [
114114- ("addRules", Add_rules);
115115- ("replaceRules", Replace_rules);
116116- ("removeRules", Remove_rules);
117117- ("setMode", Set_mode);
118118- ("addDirectories", Add_directories);
119119- ("removeDirectories", Remove_directories);
120120- ]
121121-122122- type t = {
123123- update_type : update_type;
124124- rules : Rule.t list option;
125125- behavior : Behavior.t option;
126126- mode : Mode.t option;
127127- directories : string list option;
128128- destination : destination option;
129129- unknown : Unknown.t;
130130- }
131131-132132- let create ~update_type ?rules ?behavior ?mode ?directories ?destination
133133- ?(unknown = Unknown.empty) () =
134134- { update_type; rules; behavior; mode; directories; destination; unknown }
135135-136136- let update_type t = t.update_type
137137- let rules t = t.rules
138138- let behavior t = t.behavior
139139- let mode t = t.mode
140140- let directories t = t.directories
141141- let destination t = t.destination
142142- let unknown t = t.unknown
143143-144144- let jsont : t Jsont.t =
145145- let make update_type rules behavior mode directories destination unknown =
146146- { update_type; rules; behavior; mode; directories; destination; unknown }
147147- in
148148- Jsont.Object.map ~kind:"Update" make
149149- |> Jsont.Object.mem "type" update_type_jsont ~enc:update_type
150150- |> Jsont.Object.opt_mem "rules" (Jsont.list Rule.jsont) ~enc:rules
151151- |> Jsont.Object.opt_mem "behavior" Behavior.jsont ~enc:behavior
152152- |> Jsont.Object.opt_mem "mode" Mode.jsont ~enc:mode
153153- |> Jsont.Object.opt_mem "directories" (Jsont.list Jsont.string)
154154- ~enc:directories
155155- |> Jsont.Object.opt_mem "destination" destination_jsont ~enc:destination
156156- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
157157- |> Jsont.Object.finish
158158-end
159159-160160-(** Permission context for callbacks *)
161161-module Context = struct
162162- type t = { suggestions : Update.t list; unknown : Unknown.t }
163163-164164- let create ?(suggestions = []) ?(unknown = Unknown.empty) () =
165165- { suggestions; unknown }
166166-167167- let suggestions t = t.suggestions
168168- let unknown t = t.unknown
169169-170170- let jsont : t Jsont.t =
171171- let make suggestions unknown = { suggestions; unknown } in
172172- Jsont.Object.map ~kind:"Context" make
173173- |> Jsont.Object.mem "suggestions" (Jsont.list Update.jsont) ~enc:suggestions
174174- ~dec_absent:[]
175175- |> Jsont.Object.keep_unknown Unknown.mems ~enc:unknown
176176- |> Jsont.Object.finish
177177-end
178178-179179-(** Permission results *)
180180-module Result = struct
181181- type t =
182182- | Allow of {
183183- updated_input : Jsont.json option;
184184- updated_permissions : Update.t list option;
185185- unknown : Unknown.t;
186186- }
187187- | Deny of { message : string; interrupt : bool; unknown : Unknown.t }
188188-189189- let allow ?updated_input ?updated_permissions ?(unknown = Unknown.empty) () =
190190- Allow { updated_input; updated_permissions; unknown }
191191-192192- let deny ~message ~interrupt ?(unknown = Unknown.empty) () =
193193- Deny { message; interrupt; unknown }
194194-195195- let jsont : t Jsont.t =
196196- let allow_record =
197197- let make updated_input updated_permissions unknown =
198198- Allow { updated_input; updated_permissions; unknown }
199199- in
200200- Jsont.Object.map ~kind:"AllowRecord" make
201201- |> Jsont.Object.mem "updatedInput" (Jsont.option Jsont.json)
202202- ~enc:(function
203203- | Allow { updated_input; _ } -> updated_input | _ -> None)
204204- ~dec_absent:None
205205- |> Jsont.Object.opt_mem "updatedPermissions" (Jsont.list Update.jsont)
206206- ~enc:(function
207207- | Allow { updated_permissions; _ } -> updated_permissions
208208- | _ -> None)
209209- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(function
210210- | Allow { unknown; _ } -> unknown
211211- | _ -> Unknown.empty)
212212- |> Jsont.Object.finish
213213- in
214214- let deny_record =
215215- let make message interrupt unknown =
216216- Deny { message; interrupt; unknown }
217217- in
218218- Jsont.Object.map ~kind:"DenyRecord" make
219219- |> Jsont.Object.mem "message" Jsont.string ~enc:(function
220220- | Deny { message; _ } -> message
221221- | _ -> "")
222222- |> Jsont.Object.mem "interrupt" Jsont.bool ~enc:(function
223223- | Deny { interrupt; _ } -> interrupt
224224- | _ -> false)
225225- |> Jsont.Object.keep_unknown Unknown.mems ~enc:(function
226226- | Deny { unknown; _ } -> unknown
227227- | _ -> Unknown.empty)
228228- |> Jsont.Object.finish
229229- in
230230- let case_allow =
231231- Jsont.Object.Case.map "allow" allow_record ~dec:(fun v -> v)
232232- in
233233- let case_deny =
234234- Jsont.Object.Case.map "deny" deny_record ~dec:(fun v -> v)
235235- in
236236-237237- let enc_case = function
238238- | Allow _ as v -> Jsont.Object.Case.value case_allow v
239239- | Deny _ as v -> Jsont.Object.Case.value case_deny v
240240- in
241241-242242- let cases = Jsont.Object.Case.[ make case_allow; make case_deny ] in
243243-244244- Jsont.Object.map ~kind:"Result" Fun.id
245245- |> Jsont.Object.case_mem "behavior" Jsont.string ~enc:Fun.id ~enc_case cases
246246- ~tag_to_string:Fun.id ~tag_compare:String.compare
247247- |> Jsont.Object.finish
248248-end
-227
proto/permissions.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Permission system wire format for Claude tool invocations.
77-88- This module provides the wire format encoding/decoding for permission types
99- used in the Claude protocol. It handles JSON serialization and
1010- deserialization with proper field name mappings. *)
1111-1212-(** {1 Permission Modes} *)
1313-1414-module Mode : sig
1515- (** Permission modes control the overall behavior of the permission system. *)
1616-1717- (** The type of permission modes. *)
1818- type t =
1919- | Default (** Standard permission mode with normal checks *)
2020- | Accept_edits (** Automatically accept file edits *)
2121- | Plan (** Planning mode with restricted execution *)
2222- | Bypass_permissions (** Bypass all permission checks *)
2323-2424- val jsont : t Jsont.t
2525- (** [jsont] is the Jsont codec for permission modes. Wire format uses
2626- camelCase: "default", "acceptEdits", "plan", "bypassPermissions". *)
2727-2828- val to_string : t -> string
2929- (** [to_string t] converts a mode to its wire format string representation. *)
3030-3131- val of_string : string -> t
3232- (** [of_string s] parses a mode from its wire format string representation.
3333- @raise Invalid_argument if the string is not a valid mode. *)
3434-end
3535-3636-(** {1 Permission Behaviors} *)
3737-3838-module Behavior : sig
3939- (** Behaviors determine how permission requests are handled. *)
4040-4141- (** The type of permission behaviors. *)
4242- type t =
4343- | Allow (** Allow the operation *)
4444- | Deny (** Deny the operation *)
4545- | Ask (** Ask the user for permission *)
4646-4747- val jsont : t Jsont.t
4848- (** [jsont] is the Jsont codec for permission behaviors. Wire format uses
4949- lowercase: "allow", "deny", "ask". *)
5050-5151- val to_string : t -> string
5252- (** [to_string t] converts a behavior to its wire format string
5353- representation. *)
5454-5555- val of_string : string -> t
5656- (** [of_string s] parses a behavior from its wire format string
5757- representation.
5858- @raise Invalid_argument if the string is not a valid behavior. *)
5959-end
6060-6161-(** {1 Permission Rules} *)
6262-6363-module Rule : sig
6464- (** Rules define specific permissions for tools. *)
6565-6666- type t
6767- (** The type of permission rules. *)
6868-6969- val jsont : t Jsont.t
7070- (** [jsont] is the Jsont codec for permission rules. Preserves unknown fields
7171- for forward compatibility. *)
7272-7373- val create :
7474- tool_name:string -> ?rule_content:string -> ?unknown:Unknown.t -> unit -> t
7575- (** [create ~tool_name ?rule_content ?unknown ()] creates a new rule.
7676- @param tool_name The name of the tool this rule applies to
7777- @param rule_content Optional rule specification or pattern
7878- @param unknown Optional unknown fields to preserve *)
7979-8080- val tool_name : t -> string
8181- (** [tool_name t] returns the tool name. *)
8282-8383- val rule_content : t -> string option
8484- (** [rule_content t] returns the optional rule content. *)
8585-8686- val unknown : t -> Unknown.t
8787- (** [unknown t] returns the unknown fields. *)
8888-end
8989-9090-(** {1 Permission Updates} *)
9191-9292-module Update : sig
9393- (** Updates modify permission settings. *)
9494-9595- (** The destination for permission updates. *)
9696- type destination =
9797- | User_settings (** Apply to user settings *)
9898- | Project_settings (** Apply to project settings *)
9999- | Local_settings (** Apply to local settings *)
100100- | Session (** Apply to current session only *)
101101-102102- (** The type of permission update. *)
103103- type update_type =
104104- | Add_rules (** Add new rules *)
105105- | Replace_rules (** Replace existing rules *)
106106- | Remove_rules (** Remove rules *)
107107- | Set_mode (** Set permission mode *)
108108- | Add_directories (** Add allowed directories *)
109109- | Remove_directories (** Remove allowed directories *)
110110-111111- type t
112112- (** The type of permission updates. *)
113113-114114- val jsont : t Jsont.t
115115- (** [jsont] is the Jsont codec for permission updates. Wire format uses
116116- camelCase for destination ("userSettings", "projectSettings",
117117- "localSettings", "session") and update_type ("addRules", "replaceRules",
118118- "removeRules", "setMode", "addDirectories", "removeDirectories"). *)
119119-120120- val create :
121121- update_type:update_type ->
122122- ?rules:Rule.t list ->
123123- ?behavior:Behavior.t ->
124124- ?mode:Mode.t ->
125125- ?directories:string list ->
126126- ?destination:destination ->
127127- ?unknown:Unknown.t ->
128128- unit ->
129129- t
130130- (** [create ~update_type ?rules ?behavior ?mode ?directories ?destination
131131- ?unknown ()] creates a new permission update.
132132- @param update_type The type of update to perform
133133- @param rules Optional list of rules to add/remove/replace
134134- @param behavior Optional behavior to set
135135- @param mode Optional permission mode to set
136136- @param directories Optional directories to add/remove
137137- @param destination Optional destination for the update
138138- @param unknown Optional unknown fields to preserve *)
139139-140140- val update_type : t -> update_type
141141- (** [update_type t] returns the update type. *)
142142-143143- val rules : t -> Rule.t list option
144144- (** [rules t] returns the optional list of rules. *)
145145-146146- val behavior : t -> Behavior.t option
147147- (** [behavior t] returns the optional behavior. *)
148148-149149- val mode : t -> Mode.t option
150150- (** [mode t] returns the optional mode. *)
151151-152152- val directories : t -> string list option
153153- (** [directories t] returns the optional list of directories. *)
154154-155155- val destination : t -> destination option
156156- (** [destination t] returns the optional destination. *)
157157-158158- val unknown : t -> Unknown.t
159159- (** [unknown t] returns the unknown fields. *)
160160-end
161161-162162-(** {1 Permission Context} *)
163163-164164-module Context : sig
165165- (** Context provided to permission callbacks. *)
166166-167167- type t
168168- (** The type of permission context. *)
169169-170170- val jsont : t Jsont.t
171171- (** [jsont] is the Jsont codec for permission context. Preserves unknown
172172- fields for forward compatibility. *)
173173-174174- val create : ?suggestions:Update.t list -> ?unknown:Unknown.t -> unit -> t
175175- (** [create ?suggestions ?unknown ()] creates a new context.
176176- @param suggestions Optional list of suggested permission updates
177177- @param unknown Optional unknown fields to preserve *)
178178-179179- val suggestions : t -> Update.t list
180180- (** [suggestions t] returns the list of suggested updates. *)
181181-182182- val unknown : t -> Unknown.t
183183- (** [unknown t] returns the unknown fields. *)
184184-end
185185-186186-(** {1 Permission Results} *)
187187-188188-module Result : sig
189189- (** Results of permission checks. *)
190190-191191- type t =
192192- | Allow of {
193193- updated_input : Jsont.json option; (** Modified tool input *)
194194- updated_permissions : Update.t list option;
195195- (** Permission updates to apply *)
196196- unknown : Unknown.t; (** Unknown fields *)
197197- }
198198- | Deny of {
199199- message : string; (** Reason for denial *)
200200- interrupt : bool; (** Whether to interrupt execution *)
201201- unknown : Unknown.t; (** Unknown fields *)
202202- }
203203- (** The type of permission results. Wire format uses a discriminated
204204- union with "behavior" field set to "allow" or "deny". *)
205205-206206- val jsont : t Jsont.t
207207- (** [jsont] is the Jsont codec for permission results. Preserves unknown
208208- fields for forward compatibility. *)
209209-210210- val allow :
211211- ?updated_input:Jsont.json ->
212212- ?updated_permissions:Update.t list ->
213213- ?unknown:Unknown.t ->
214214- unit ->
215215- t
216216- (** [allow ?updated_input ?updated_permissions ?unknown ()] creates an allow
217217- result.
218218- @param updated_input Optional modified tool input
219219- @param updated_permissions Optional permission updates to apply
220220- @param unknown Optional unknown fields to preserve *)
221221-222222- val deny : message:string -> interrupt:bool -> ?unknown:Unknown.t -> unit -> t
223223- (** [deny ~message ~interrupt ?unknown ()] creates a deny result.
224224- @param message The reason for denying permission
225225- @param interrupt Whether to interrupt further execution
226226- @param unknown Optional unknown fields to preserve *)
227227-end
-17
proto/structured_output.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Structured output wire format implementation. *)
77-88-type t = { json_schema : Jsont.json }
99-1010-let of_json_schema schema = { json_schema = schema }
1111-let to_json_schema t = t.json_schema
1212-1313-(* Codec for serializing structured output format to wire protocol *)
1414-let jsont : t Jsont.t =
1515- Jsont.Object.map ~kind:"StructuredOutput" (fun json_schema -> { json_schema })
1616- |> Jsont.Object.mem "jsonSchema" Jsont.json ~enc:(fun t -> t.json_schema)
1717- |> Jsont.Object.finish
-67
proto/structured_output.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Structured output configuration using JSON Schema.
77-88- This module provides the wire format types for structured output support,
99- allowing specification of expected output formats using JSON schemas. When a
1010- structured output format is configured, Claude will return its response in
1111- the specified JSON format, validated against the provided schema.
1212-1313- This is the protocol-level module. For the high-level API with logging and
1414- additional features, see {!Claude.Structured_output}. *)
1515-1616-(** {1 Output Format Configuration} *)
1717-1818-type t
1919-(** The type of structured output format configurations.
2020-2121- This wraps a JSON Schema that specifies the expected output format. *)
2222-2323-val of_json_schema : Jsont.json -> t
2424-(** [of_json_schema schema] creates an output format from a JSON Schema.
2525-2626- The schema should be a valid JSON Schema Draft 7 as a {!type:Jsont.json}
2727- value.
2828-2929- Example:
3030- {[
3131- let meta = Jsont.Meta.none in
3232- let schema =
3333- Jsont.Object
3434- ( [
3535- (("type", meta), Jsont.String ("object", meta));
3636- ( ("properties", meta),
3737- Jsont.Object
3838- ( [
3939- ( ("name", meta),
4040- Jsont.Object
4141- ([ (("type", meta), Jsont.String ("string", meta)) ], meta)
4242- );
4343- ( ("age", meta),
4444- Jsont.Object
4545- ([ (("type", meta), Jsont.String ("integer", meta)) ], meta)
4646- );
4747- ],
4848- meta ) );
4949- ( ("required", meta),
5050- Jsont.Array
5151- ([ Jsont.String ("name", meta); Jsont.String ("age", meta) ], meta)
5252- );
5353- ],
5454- meta )
5555- in
5656-5757- let format = Structured_output.of_json_schema schema
5858- ]} *)
5959-6060-val to_json_schema : t -> Jsont.json
6161-(** [to_json_schema t] extracts the JSON Schema from the output format. *)
6262-6363-val jsont : t Jsont.t
6464-(** Codec for structured output format.
6565-6666- Encodes/decodes the structured output configuration to/from the wire format
6767- JSON representation used by the Claude CLI protocol. *)
-64
proto/unknown.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Unknown fields for preserving extra JSON object members during
77- round-tripping.
88-99- This module provides an opaque type for storing unknown JSON fields as an
1010- association list. This is useful for preserving fields that are not part of
1111- the defined schema but should be maintained when reading and writing JSON.
1212-*)
1313-1414-type t = (string * Jsont.json) list
1515-1616-let empty = []
1717-let is_empty = function [] -> true | _ -> false
1818-let of_assoc x = x
1919-let to_assoc x = x
2020-2121-let jsont =
2222- let open Jsont in
2323- let dec obj =
2424- match obj with
2525- | Object (fields, _) ->
2626- (* Convert from Jsont.mem list (name * json) to (string * json) list *)
2727- List.map (fun ((name, _meta), json) -> (name, json)) fields
2828- | _ -> invalid_arg "Expected object"
2929- in
3030- let enc fields =
3131- (* Convert from (string * json) list to Jsont.mem list *)
3232- let mems =
3333- List.map (fun (name, json) -> ((name, Meta.none), json)) fields
3434- in
3535- Object (mems, Meta.none)
3636- in
3737- map ~dec ~enc json
3838-3939-(** Mems codec for use with Jsont.Object.keep_unknown.
4040-4141- This provides a custom mems codec that converts between our (string *
4242- Jsont.json) list representation and the Jsont.mem list representation used
4343- by keep_unknown. *)
4444-let mems : (t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
4545- let open Jsont in
4646- (* The decoder builds up a mem list (the third type parameter) and
4747- dec_finish converts it to our type t *)
4848- let dec_empty () = [] in
4949- let dec_add meta name json acc = ((name, meta), json) :: acc in
5050- let dec_finish _meta mems =
5151- (* Convert from mem list to (string * json) list *)
5252- List.rev_map (fun ((name, _meta), json) -> (name, json)) mems
5353- in
5454- let enc =
5555- {
5656- Object.Mems.enc =
5757- (fun k fields acc ->
5858- List.fold_left
5959- (fun acc (name, json) -> k Meta.none name json acc)
6060- acc fields);
6161- }
6262- in
6363- Object.Mems.map ~kind:"Unknown" ~dec_empty ~dec_add ~dec_finish ~enc
6464- Jsont.json
-34
proto/unknown.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Unknown fields for preserving extra JSON object members during
77- round-tripping.
88-99- This module provides an opaque type for storing unknown JSON fields as an
1010- association list. This is useful for preserving fields that are not part of
1111- the defined schema but should be maintained when reading and writing JSON.
1212-*)
1313-1414-type t
1515-(** The opaque type of unknown fields, stored as an association list of field
1616- names to JSON values. *)
1717-1818-val empty : t
1919-(** [empty] is an empty set of unknown fields. *)
2020-2121-val is_empty : t -> bool
2222-(** [is_empty t] returns [true] if there are no unknown fields stored in [t]. *)
2323-2424-val of_assoc : (string * Jsont.json) list -> t
2525-(** [of_assoc assoc] creates unknown fields from an association list. *)
2626-2727-val to_assoc : t -> (string * Jsont.json) list
2828-(** [to_assoc t] returns the association list of unknown fields. *)
2929-3030-val jsont : t Jsont.t
3131-(** [jsont] is a codec for encoding and decoding unknown fields to/from JSON. *)
3232-3333-val mems : (t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map
3434-(** [mems] is a mems codec for use with [Jsont.Object.keep_unknown]. *)
-35
test/README.md
···11-# Claude IO Test Suite
22-33-This directory contains test programs for the Claude IO OCaml library.
44-55-## Available Tests
66-77-### camel_jokes
88-A fun demonstration that runs three concurrent Claude instances to generate camel jokes.
99-Tests concurrent client handling and basic message processing.
1010-1111-### permission_demo
1212-An interactive demonstration of Claude's permission system.
1313-Shows how to implement custom permission callbacks and grant/deny access to tools dynamically.
1414-1515-## Running Tests
1616-1717-```bash
1818-# Run the camel joke competition
1919-dune exec camel_jokes
2020-2121-# Run the permission demo (interactive)
2222-dune exec permission_demo
2323-2424-# With verbose output to see message flow
2525-dune exec permission_demo -- -v
2626-```
2727-2828-## Features Tested
2929-3030-- Concurrent Claude client instances
3131-- Message handling and processing
3232-- Permission callbacks
3333-- Tool access control
3434-- Typed message API
3535-- Pretty printing of messages
-112
test/TEST.md
···11-# Claude Library Architecture Summary
22-33-This document summarizes the architecture of the OCaml Eio Claude library located in `../lib`.
44-55-## Overview
66-77-The Claude library is a high-quality OCaml Eio wrapper around the Claude Code CLI that provides structured JSON streaming communication with Claude. It follows a clean layered architecture with strong typing and comprehensive error handling.
88-99-## Core Architecture
1010-1111-The library is organized into several focused modules that work together to provide a complete Claude integration:
1212-1313-### 1. Transport Layer (`Transport`)
1414-- **Purpose**: Low-level CLI process management and communication
1515-- **Key Functions**:
1616- - Spawns and manages the `claude` CLI process using Eio's process manager
1717- - Handles bidirectional JSON streaming via stdin/stdout
1818- - Provides `send`/`receive_line` primitives with proper resource cleanup
1919-- **Integration**: Forms the foundation for all Claude communication
2020-2121-### 2. Message Protocol Layer
2222-2323-#### Content Blocks (`Content_block`)
2424-- **Purpose**: Defines the building blocks of Claude messages
2525-- **Types**: Text, Tool_use, Tool_result, Thinking blocks
2626-- **Key Features**: Each block type has specialized accessors and JSON serialization
2727-- **Integration**: Used by messages to represent diverse content types
2828-2929-#### Messages (`Message`)
3030-- **Purpose**: Structured message types for Claude communication
3131-- **Types**: User, Assistant, System, Result messages
3232-- **Key Features**:
3333- - User messages support both simple strings and complex content blocks
3434- - Assistant messages include model info and mixed content
3535- - System messages handle session control
3636- - Result messages provide conversation metadata and usage stats
3737-- **Integration**: Primary data structures exchanged between client and Claude
3838-3939-#### Control Messages (`Control`)
4040-- **Purpose**: Session management and control flow
4141-- **Key Features**: Request IDs, subtypes, and arbitrary JSON data payload
4242-- **Integration**: Used for session initialization, cancellation, and other operational commands
4343-4444-### 3. Permission System (`Permissions`)
4545-- **Purpose**: Fine-grained control over Claude's tool usage
4646-- **Components**:
4747- - **Modes**: Default, Accept_edits, Plan, Bypass_permissions
4848- - **Rules**: Tool-specific permission specifications
4949- - **Callbacks**: Custom permission logic with context and suggestions
5050- - **Results**: Allow/Deny decisions with optional modifications
5151-- **Integration**: Consulted by client before allowing tool invocations
5252-5353-### 4. Configuration (`Options`)
5454-- **Purpose**: Session configuration and behavior control
5555-- **Features**:
5656- - Tool allow/disallow lists
5757- - System prompt customization (replace or append)
5858- - Model selection and thinking token limits
5959- - Working directory and environment variables
6060-- **Integration**: Passed to transport layer and used throughout the session
6161-- **Pattern**: Builder pattern with `with_*` functions for immutable updates
6262-6363-### 5. Client Interface (`Client`)
6464-- **Purpose**: High-level API for Claude interactions
6565-- **Key Functions**:
6666- - Session creation and management
6767- - Message sending (`query`, `send_message`, `send_user_message`)
6868- - Response streaming (`receive`, `receive_all`)
6969- - Permission discovery and callback management
7070-- **Integration**: Orchestrates all other modules to provide the main user API
7171-7272-### 6. Main Module (`Claude`)
7373-- **Purpose**: Public API facade with comprehensive documentation
7474-- **Features**:
7575- - Re-exports all sub-modules
7676- - Extensive usage examples and architectural documentation
7777- - Logging configuration guidance
7878-- **Integration**: Single entry point for library users
7979-8080-## Data Flow
8181-8282-1. **Configuration**: Options are created with desired settings
8383-2. **Transport**: Client creates transport layer with CLI process
8484-3. **Message Exchange**:
8585- - User messages are sent via JSON streaming
8686- - Claude responses are received as streaming JSON
8787- - Messages are parsed into strongly-typed structures
8888-4. **Permission Checking**: Tool usage is filtered through permission system
8989-5. **Content Processing**: Response content blocks are extracted and processed
9090-6. **Session Management**: Control messages handle session lifecycle
9191-9292-## Key Design Principles
9393-9494-- **Eio Integration**: Native use of Eio's concurrency primitives (Switch, Process.mgr)
9595-- **Type Safety**: Comprehensive typing with specific error exceptions
9696-- **Streaming**: Efficient processing via `Message.t Seq.t` sequences
9797-- **Modularity**: Clear separation of concerns with minimal inter-dependencies
9898-- **Documentation**: Extensive interface documentation with usage examples
9999-- **Error Handling**: Specific exception types for different failure modes
100100-- **Logging**: Structured logging with per-module sources using the Logs library
101101-102102-## Usage Patterns
103103-104104-The library supports both simple text queries and complex multi-turn conversations:
105105-106106-- **Simple Queries**: `Client.query` with text input
107107-- **Tool Control**: Permission callbacks and allow/disallow lists
108108-- **Streaming**: Process responses as they arrive via sequences
109109-- **Session Management**: Full control over Claude's execution environment
110110-- **Custom Prompts**: System prompt replacement and augmentation
111111-112112-The architecture enables fine-grained control over Claude's capabilities while maintaining ease of use for common scenarios.
-162
test/advanced_config_demo.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(* Advanced Configuration Demo
77-88- This example demonstrates the advanced configuration options available
99- in the OCaml Claude SDK, including:
1010- - Budget limits for cost control
1111- - Fallback models for reliability
1212- - Settings isolation for CI/CD environments
1313- - Custom buffer sizes for large outputs
1414-*)
1515-1616-open Eio.Std
1717-open Claude
1818-1919-let log_setup () =
2020- Logs.set_reporter (Logs_fmt.reporter ());
2121- Logs.set_level (Some Logs.Info)
2222-2323-(* Example 1: CI/CD Configuration
2424-2525- In CI/CD environments, you want isolated, reproducible behavior
2626- without any user/project/local settings interfering.
2727-*)
2828-let ci_cd_config () =
2929- Options.default |> Options.with_no_settings (* Disable all settings loading *)
3030- |> Options.with_max_budget_usd 0.50 (* 50 cent limit per run *)
3131- |> Options.with_fallback_model (Claude.Proto.Model.of_string "claude-haiku-4")
3232- (* Fast fallback *)
3333- |> Options.with_model (Claude.Proto.Model.of_string "claude-sonnet-4-5")
3434- |> Options.with_permission_mode Permissions.Mode.Bypass_permissions
3535-3636-(* Example 2: Production Configuration with Fallback
3737-3838- Production usage with cost controls and automatic fallback
3939- to ensure availability.
4040-*)
4141-let production_config () =
4242- Options.default
4343- |> Options.with_model (Claude.Proto.Model.of_string "claude-sonnet-4-5")
4444- |> Options.with_fallback_model
4545- (Claude.Proto.Model.of_string "claude-sonnet-3-5")
4646- |> Options.with_max_budget_usd 10.0 (* $10 limit *)
4747- |> Options.with_max_buffer_size 5_000_000 (* 5MB buffer for large outputs *)
4848-4949-(* Example 3: Development Configuration
5050-5151- Development with user settings enabled but with cost controls.
5252-*)
5353-let dev_config () =
5454- Options.default
5555- (* Note: Settings are loaded by default from user/project/local files *)
5656- |> Options.with_max_budget_usd 1.0 (* $1 limit for dev testing *)
5757- |> Options.with_fallback_model (Claude.Proto.Model.of_string "claude-haiku-4")
5858-5959-(* Example 4: Isolated Test Configuration
6060-6161- For automated testing with no external settings and strict limits.
6262-*)
6363-let test_config () =
6464- Options.default |> Options.with_no_settings
6565- |> Options.with_max_budget_usd 0.10 (* 10 cent limit per test *)
6666- |> Options.with_model (Claude.Proto.Model.of_string "claude-haiku-4")
6767- (* Fast, cheap model *)
6868- |> Options.with_permission_mode Permissions.Mode.Bypass_permissions
6969- |> Options.with_max_buffer_size 1_000_000 (* 1MB buffer *)
7070-7171-(* Example 5: Custom Buffer Size Demo
7272-7373- For applications that need to handle very large outputs.
7474-*)
7575-let _large_output_config () =
7676- Options.default
7777- |> Options.with_max_buffer_size 10_000_000 (* 10MB buffer *)
7878- |> Options.with_model (Claude.Proto.Model.of_string "claude-sonnet-4-5")
7979-8080-(* Helper to run a query with a specific configuration *)
8181-let run_query ~sw process_mgr clock config prompt =
8282- print_endline "\n=== Configuration ===";
8383- (match Options.max_budget_usd config with
8484- | Some budget -> Printf.printf "Budget limit: $%.2f\n" budget
8585- | None -> print_endline "Budget limit: None");
8686- (match Options.fallback_model config with
8787- | Some model ->
8888- Printf.printf "Fallback model: %s\n" (Claude.Proto.Model.to_string model)
8989- | None -> print_endline "Fallback model: None");
9090- (* Settings configuration display removed - API doesn't expose setting_sources *)
9191- print_endline "Settings: Default (user/project/local files)";
9292- (match Options.max_buffer_size config with
9393- | Some size -> Printf.printf "Buffer size: %d bytes\n" size
9494- | None -> print_endline "Buffer size: Default (1MB)");
9595-9696- print_endline "\n=== Running Query ===";
9797- let client = Client.create ~options:config ~sw ~process_mgr ~clock () in
9898- Client.query client prompt;
9999- let responses = Client.receive client in
100100-101101- Seq.iter
102102- (function
103103- | Response.Text text ->
104104- Printf.printf "Response: %s\n" (Response.Text.content text)
105105- | Response.Complete result ->
106106- Printf.printf "\n=== Session Complete ===\n";
107107- Printf.printf "Duration: %dms\n"
108108- (Response.Complete.duration_ms result);
109109- (match Response.Complete.total_cost_usd result with
110110- | Some cost -> Printf.printf "Cost: $%.4f\n" cost
111111- | None -> ());
112112- Printf.printf "Turns: %d\n" (Response.Complete.num_turns result)
113113- | _ -> ())
114114- responses
115115-116116-let main () =
117117- log_setup ();
118118-119119- Eio_main.run @@ fun env ->
120120- Switch.run @@ fun sw ->
121121- let process_mgr = Eio.Stdenv.process_mgr env in
122122- let clock = Eio.Stdenv.clock env in
123123-124124- print_endline "==============================================";
125125- print_endline "Claude SDK - Advanced Configuration Examples";
126126- print_endline "==============================================";
127127-128128- (* Example: CI/CD isolated environment *)
129129- print_endline "\n\n### Example 1: CI/CD Configuration ###";
130130- print_endline "Purpose: Isolated, reproducible environment for CI/CD";
131131- let config = ci_cd_config () in
132132- run_query ~sw process_mgr clock config "What is 2+2? Answer in one sentence.";
133133-134134- (* Example: Production with fallback *)
135135- print_endline "\n\n### Example 2: Production Configuration ###";
136136- print_endline "Purpose: Production with cost controls and fallback";
137137- let config = production_config () in
138138- run_query ~sw process_mgr clock config "Explain OCaml in one sentence.";
139139-140140- (* Example: Development with settings *)
141141- print_endline "\n\n### Example 3: Development Configuration ###";
142142- print_endline "Purpose: Development with user/project settings";
143143- let config = dev_config () in
144144- run_query ~sw process_mgr clock config
145145- "What is functional programming? One sentence.";
146146-147147- (* Example: Test configuration *)
148148- print_endline "\n\n### Example 4: Test Configuration ###";
149149- print_endline "Purpose: Automated testing with strict limits";
150150- let config = test_config () in
151151- run_query ~sw process_mgr clock config "Say 'test passed' in one word.";
152152-153153- print_endline "\n\n==============================================";
154154- print_endline "All examples completed successfully!";
155155- print_endline "=============================================="
156156-157157-let () =
158158- try main ()
159159- with e ->
160160- Printf.eprintf "Error: %s\n" (Printexc.to_string e);
161161- Printexc.print_backtrace stderr;
162162- exit 1
-139
test/camel_jokes.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-open Eio.Std
77-88-let src = Logs.Src.create "camel_jokes" ~doc:"Camel joke competition"
99-1010-module Log = (val Logs.src_log src : Logs.LOG)
1111-1212-let process_claude_response client name =
1313- Log.info (fun m -> m "=== %s's Response ===" name);
1414- let responses = Claude.Client.receive_all client in
1515- List.iter
1616- (fun resp ->
1717- match resp with
1818- | Claude.Response.Text t ->
1919- let text = Claude.Response.Text.content t in
2020- Log.app (fun m -> m "%s: %s" name text)
2121- | Claude.Response.Tool_use t ->
2222- Log.debug (fun m ->
2323- m "%s using tool: %s" name (Claude.Response.Tool_use.name t))
2424- | Claude.Response.Thinking t ->
2525- Log.debug (fun m ->
2626- m "%s thinking: %s" name (Claude.Response.Thinking.content t))
2727- | Claude.Response.Complete c ->
2828- (if Claude.Response.Complete.total_cost_usd c <> None then
2929- let cost =
3030- Option.get (Claude.Response.Complete.total_cost_usd c)
3131- in
3232- Log.info (fun m -> m "%s's joke cost: $%.6f" name cost));
3333- Log.debug (fun m ->
3434- m "%s session: %s, duration: %dms" name
3535- (Claude.Response.Complete.session_id c)
3636- (Claude.Response.Complete.duration_ms c))
3737- | Claude.Response.Error e ->
3838- Log.err (fun m ->
3939- m "Error from %s: %s" name (Claude.Response.Error.message e))
4040- | Claude.Response.Init _ ->
4141- (* Init messages are already logged by the library *)
4242- ()
4343- | Claude.Response.Tool_result _ ->
4444- (* Tool results are user messages, skip *)
4545- ())
4646- responses
4747-4848-let run_claude ~sw ~env name prompt =
4949- Log.info (fun m -> m "🐪 Starting %s..." name);
5050- let options =
5151- Claude.Options.default
5252- |> Claude.Options.with_model (Claude.Model.of_string "sonnet")
5353- |> Claude.Options.with_allowed_tools []
5454- in
5555-5656- let client =
5757- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
5858- ~clock:env#clock ()
5959- in
6060-6161- Claude.Client.query client prompt;
6262- process_claude_response client name
6363-6464-let main ~env =
6565- Switch.run @@ fun sw ->
6666- Log.app (fun m -> m "🐪 Starting the Great Camel Joke Competition! 🐪");
6767- Log.app (fun m -> m "================================================\n");
6868-6969- let prompts =
7070- [
7171- ( "Claude 1",
7272- "Tell me a short, funny joke about camels! Make it original and clever."
7373- );
7474- ( "Claude 2",
7575- "Give me your best camel joke - something witty and unexpected!" );
7676- ("Claude 3", "Share a hilarious camel joke that will make everyone laugh!");
7777- ]
7878- in
7979-8080- (* Run all three Claudes concurrently *)
8181- Fiber.all
8282- (List.map
8383- (fun (name, prompt) -> fun () -> run_claude ~sw ~env name prompt)
8484- prompts);
8585-8686- Log.app (fun m -> m "\n================================================");
8787- Log.app (fun m -> m "🎉 The Camel Joke Competition is complete! 🎉")
8888-8989-(* Command-line interface *)
9090-open Cmdliner
9191-9292-let main_term env =
9393- let setup_log style_renderer level =
9494- Fmt_tty.setup_std_outputs ?style_renderer ();
9595- Logs.set_level level;
9696- Logs.set_reporter (Logs_fmt.reporter ());
9797- (* Set default to App level if not specified *)
9898- if level = None then Logs.set_level (Some Logs.App);
9999- (* Enable debug for Client module if in debug mode *)
100100- if level = Some Logs.Debug then
101101- Logs.Src.set_level Claude.Client.src (Some Logs.Debug)
102102- in
103103- let run style level =
104104- setup_log style level;
105105- main ~env
106106- in
107107- Term.(const run $ Fmt_cli.style_renderer () $ Logs_cli.level ())
108108-109109-let cmd env =
110110- let doc = "Run the Great Camel Joke Competition using Claude" in
111111- let man =
112112- [
113113- `S Manpage.s_description;
114114- `P
115115- "This program runs three concurrent Claude instances to generate camel \
116116- jokes.";
117117- `P "Use $(b,-v) or $(b,--verbosity=info) to see RPC message traffic.";
118118- `P
119119- "Use $(b,-vv) or $(b,--verbosity=debug) to see all internal operations.";
120120- `S Manpage.s_examples;
121121- `P "Run with normal output:";
122122- `Pre " $(mname)";
123123- `P "Run with info-level logging (RPC traffic):";
124124- `Pre " $(mname) -v";
125125- `Pre " $(mname) --verbosity=info";
126126- `P "Run with debug logging (all operations):";
127127- `Pre " $(mname) -vv";
128128- `Pre " $(mname) --verbosity=debug";
129129- `P "Enable debug for specific modules:";
130130- `Pre " LOGS='claude.transport=debug' $(mname)";
131131- `Pre " LOGS='claude.message=info,camel_jokes=debug' $(mname)";
132132- `S Manpage.s_bugs;
133133- `P "Report bugs at https://github.com/your-repo/issues";
134134- ]
135135- in
136136- let info = Cmd.info "camel_jokes" ~version:"1.0" ~doc ~man in
137137- Cmd.v info (main_term env)
138138-139139-let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))
-111
test/discovery_demo.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-open Eio.Std
77-88-let src =
99- Logs.Src.create "discovery_demo" ~doc:"Permission discovery demonstration"
1010-1111-module Log = (val Logs.src_log src : Logs.LOG)
1212-1313-let process_response client =
1414- let responses = Claude.Client.receive_all client in
1515- List.iter
1616- (fun resp ->
1717- match resp with
1818- | Claude.Response.Text text ->
1919- let content = Claude.Response.Text.content text in
2020- Log.app (fun m ->
2121- m "Claude: %s"
2222- (if String.length content > 100 then
2323- String.sub content 0 100 ^ "..."
2424- else content))
2525- | Claude.Response.Tool_use t ->
2626- Log.info (fun m -> m "Tool use: %s" (Claude.Response.Tool_use.name t))
2727- | Claude.Response.Error err ->
2828- Log.err (fun m -> m "Error: %s" (Claude.Response.Error.message err))
2929- | Claude.Response.Complete result -> (
3030- match Claude.Response.Complete.total_cost_usd result with
3131- | Some cost -> Log.info (fun m -> m "Cost: $%.6f" cost)
3232- | None -> ())
3333- | _ -> ())
3434- responses
3535-3636-let run_discovery ~sw ~env =
3737- Log.app (fun m -> m "🔍 Permission Discovery Demo");
3838- Log.app (fun m -> m "=============================");
3939- Log.app (fun m -> m "This will discover what permissions Claude needs.\n");
4040-4141- (* Create client with discovery mode *)
4242- let options =
4343- Claude.Options.default
4444- |> Claude.Options.with_model (Claude.Proto.Model.of_string "sonnet")
4545- in
4646- let client =
4747- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
4848- ~clock:env#clock ()
4949- in
5050- Claude.Client.enable_permission_discovery client;
5151-5252- (* Send a prompt that will need permissions *)
5353- Log.app (fun m -> m "Asking Claude to read a secret file...");
5454- Claude.Client.query client
5555- "Please read the file test/secret_data.txt and tell me what the secret \
5656- code is.";
5757- process_response client;
5858-5959- (* Check what permissions were requested *)
6060- let permissions = Claude.Client.discovered_permissions client in
6161- if permissions = [] then
6262- Log.app (fun m ->
6363- m
6464- "\n\
6565- 📋 No permissions were requested (Claude may have used its \
6666- knowledge).")
6767- else begin
6868- Log.app (fun m -> m "\n📋 Permissions that were requested:");
6969- List.iter
7070- (fun rule ->
7171- Log.app (fun m ->
7272- m " - Tool: %s%s"
7373- (Claude.Permissions.Rule.tool_name rule)
7474- (match Claude.Permissions.Rule.rule_content rule with
7575- | Some content -> Printf.sprintf " (rule: %s)" content
7676- | None -> "")))
7777- permissions
7878- end
7979-8080-let main ~env = Switch.run @@ fun sw -> run_discovery ~sw ~env
8181-8282-(* Command-line interface *)
8383-open Cmdliner
8484-8585-let main_term env =
8686- let setup_log style_renderer level =
8787- Fmt_tty.setup_std_outputs ?style_renderer ();
8888- Logs.set_level level;
8989- Logs.set_reporter (Logs_fmt.reporter ());
9090- if level = None then Logs.set_level (Some Logs.App)
9191- in
9292- let run style level =
9393- setup_log style level;
9494- main ~env
9595- in
9696- Term.(const run $ Fmt_cli.style_renderer () $ Logs_cli.level ())
9797-9898-let cmd env =
9999- let doc = "Discover what permissions Claude needs" in
100100- let man =
101101- [
102102- `S Manpage.s_description;
103103- `P
104104- "This program runs Claude in discovery mode to see what permissions it \
105105- requests.";
106106- ]
107107- in
108108- let info = Cmd.info "discovery_demo" ~version:"1.0" ~doc ~man in
109109- Cmd.v info (main_term env)
110110-111111-let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))
-89
test/dynamic_control_demo.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-open Claude
77-open Eio.Std
88-99-let () = Logs.set_reporter (Logs_fmt.reporter ())
1010-let () = Logs.set_level (Some Logs.Info)
1111-1212-let run env =
1313- Switch.run @@ fun sw ->
1414- let process_mgr = Eio.Stdenv.process_mgr env in
1515- let clock = Eio.Stdenv.clock env in
1616-1717- (* Create client with default options *)
1818- let options = Options.default in
1919- let client = Client.create ~options ~sw ~process_mgr ~clock () in
2020-2121- traceln "=== Dynamic Control Demo ===\n";
2222-2323- (* First query with default model *)
2424- traceln "1. Initial query with default model";
2525- Client.query client "What model are you?";
2626-2727- (* Consume initial responses *)
2828- let responses = Client.receive_all client in
2929- List.iter
3030- (function
3131- | Response.Text text ->
3232- traceln "Assistant: %s" (Response.Text.content text)
3333- | _ -> ())
3434- responses;
3535-3636- traceln "\n2. Getting server info...";
3737- (try
3838- let info = Client.get_server_info client in
3939- traceln "Server version: %s" (Claude.Server_info.version info);
4040- traceln "Capabilities: [%s]"
4141- (String.concat ", " (Claude.Server_info.capabilities info));
4242- traceln "Commands: [%s]"
4343- (String.concat ", " (Claude.Server_info.commands info));
4444- traceln "Output styles: [%s]"
4545- (String.concat ", " (Claude.Server_info.output_styles info))
4646- with
4747- | Failure msg -> traceln "Failed to get server info: %s" msg
4848- | exn -> traceln "Error getting server info: %s" (Printexc.to_string exn));
4949-5050- traceln "\n3. Switching to a different model (if available)...";
5151- (try
5252- Client.set_model client (Proto.Model.of_string "claude-sonnet-4");
5353- traceln "Model switched successfully";
5454-5555- (* Query with new model *)
5656- Client.query client "Confirm your model again please.";
5757- let responses = Client.receive_all client in
5858- List.iter
5959- (function
6060- | Response.Text text ->
6161- traceln "Assistant (new model): %s" (Response.Text.content text)
6262- | _ -> ())
6363- responses
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 run env with
8282- | Transport.CLI_not_found msg ->
8383- traceln "Error: %s" msg;
8484- traceln "Make sure the 'claude' CLI is installed and authenticated.";
8585- exit 1
8686- | exn ->
8787- traceln "Unexpected error: %s" (Printexc.to_string exn);
8888- Printexc.print_backtrace stderr;
8989- exit 1
-123
test/hooks_example.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-open Eio.Std
77-88-let src = Logs.Src.create "hooks_example" ~doc:"Hooks example"
99-1010-module Log = (val Logs.src_log src : Logs.LOG)
1111-1212-(* Example 1: Block dangerous bash commands *)
1313-let block_dangerous_bash input =
1414- if input.Claude.Hooks.PreToolUse.tool_name = "Bash" then
1515- match
1616- Claude.Tool_input.get_string input.Claude.Hooks.PreToolUse.tool_input
1717- "command"
1818- with
1919- | Some command ->
2020- if String.length command >= 6 && String.sub command 0 6 = "rm -rf" then begin
2121- Log.app (fun m -> m "🚫 Blocked dangerous command: %s" command);
2222- Claude.Hooks.PreToolUse.deny
2323- ~reason:"Command contains dangerous 'rm -rf' pattern" ()
2424- end
2525- else Claude.Hooks.PreToolUse.continue ()
2626- | _ -> Claude.Hooks.PreToolUse.continue ()
2727- else Claude.Hooks.PreToolUse.continue ()
2828-2929-(* Example 2: Log all tool usage *)
3030-let log_tool_usage input =
3131- Log.app (fun m ->
3232- m "📝 Tool %s called" input.Claude.Hooks.PreToolUse.tool_name);
3333- Claude.Hooks.PreToolUse.continue ()
3434-3535-let run_example ~sw ~env =
3636- Log.app (fun m -> m "🔧 Hooks System Example");
3737- Log.app (fun m -> m "====================\n");
3838-3939- (* Configure hooks *)
4040- let hooks =
4141- Claude.Hooks.empty
4242- |> Claude.Hooks.on_pre_tool_use log_tool_usage
4343- |> Claude.Hooks.on_pre_tool_use ~pattern:"Bash" block_dangerous_bash
4444- in
4545-4646- let options =
4747- Claude.Options.default
4848- |> Claude.Options.with_model (Claude.Model.of_string "sonnet")
4949- |> Claude.Options.with_hooks hooks
5050- in
5151-5252- let client =
5353- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
5454- ~clock:env#clock ()
5555- in
5656-5757- (* Test 1: Safe command (should work) *)
5858- Log.app (fun m -> m "Test 1: Safe bash command");
5959- Claude.Client.query client "Run the bash command: echo 'Hello from hooks!'";
6060-6161- let messages = Claude.Client.receive_all client in
6262- List.iter
6363- (fun resp ->
6464- match resp with
6565- | Claude.Response.Text text ->
6666- let content = Claude.Response.Text.content text in
6767- if String.length content > 0 then
6868- Log.app (fun m -> m "Claude: %s" content)
6969- | Claude.Response.Complete _ -> Log.app (fun m -> m "✅ Test 1 complete\n")
7070- | Claude.Response.Error err ->
7171- Log.err (fun m -> m "❌ Error: %s" (Claude.Response.Error.message err))
7272- | _ -> ())
7373- messages;
7474-7575- (* Test 2: Dangerous command (should be blocked) *)
7676- Log.app (fun m -> m "Test 2: Dangerous bash command (should be blocked)");
7777- Claude.Client.query client "Run the bash command: rm -rf /tmp/test";
7878-7979- let messages = Claude.Client.receive_all client in
8080- List.iter
8181- (fun resp ->
8282- match resp with
8383- | Claude.Response.Text text ->
8484- let content = Claude.Response.Text.content text in
8585- if String.length content > 0 then
8686- Log.app (fun m -> m "Claude: %s" content)
8787- | Claude.Response.Complete _ -> Log.app (fun m -> m "✅ Test 2 complete")
8888- | Claude.Response.Error err ->
8989- Log.err (fun m -> m "❌ Error: %s" (Claude.Response.Error.message err))
9090- | _ -> ())
9191- messages;
9292-9393- Log.app (fun m -> m "\n====================");
9494- Log.app (fun m -> m "✨ Example complete!")
9595-9696-let main ~env = Switch.run @@ fun sw -> run_example ~sw ~env
9797-9898-(* Command-line interface *)
9999-open Cmdliner
100100-101101-let main_term env =
102102- let setup_log style_renderer level =
103103- Fmt_tty.setup_std_outputs ?style_renderer ();
104104- Logs.set_level level;
105105- Logs.set_reporter (Logs_fmt.reporter ());
106106- if level = None then Logs.set_level (Some Logs.App);
107107- match level with
108108- | Some Logs.Info | Some Logs.Debug ->
109109- Logs.Src.set_level Claude.Client.src (Some Logs.Info)
110110- | _ -> ()
111111- in
112112- let run style level =
113113- setup_log style level;
114114- main ~env
115115- in
116116- Term.(const run $ Fmt_cli.style_renderer () $ Logs_cli.level ())
117117-118118-let cmd env =
119119- let doc = "Demonstrate Claude's hooks system" in
120120- let info = Cmd.info "hooks_example" ~version:"1.0" ~doc in
121121- Cmd.v info (main_term env)
122122-123123-let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))
-243
test/permission_demo.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-open Eio.Std
77-88-let src =
99- Logs.Src.create "permission_demo" ~doc:"Permission callback demonstration"
1010-1111-module Log = (val Logs.src_log src : Logs.LOG)
1212-1313-(* Mutable state to track what permissions have been granted *)
1414-module Granted = struct
1515- module StringSet = Set.Make (String)
1616-1717- let tools = ref StringSet.empty
1818-1919- let grant tool_name =
2020- tools := StringSet.add tool_name !tools;
2121- Log.app (fun m -> m "✅ Permission granted for: %s" tool_name)
2222-2323- let deny tool_name =
2424- Log.app (fun m -> m "❌ Permission denied for: %s" tool_name)
2525-2626- let is_granted tool_name = StringSet.mem tool_name !tools
2727-2828- let list () =
2929- if StringSet.is_empty !tools then
3030- Log.app (fun m -> m "No permissions granted yet")
3131- else
3232- Log.app (fun m ->
3333- m "Currently granted permissions: %s"
3434- (StringSet.elements !tools |> String.concat ", "))
3535-end
3636-3737-(* Interactive permission callback *)
3838-let interactive_permission_callback ctx =
3939- let open Claude.Permissions in
4040- let tool_name = ctx.tool_name in
4141- let input = ctx.input in
4242-4343- Log.info (fun m -> m "🔔 Permission callback invoked for tool: %s" tool_name);
4444- Log.app (fun m -> m "\n🔐 PERMISSION REQUEST 🔐");
4545- Log.app (fun m -> m "Tool: %s" tool_name);
4646-4747- (* Log the full input for debugging *)
4848- let input_json = Claude.Tool_input.to_json input in
4949- Log.info (fun m ->
5050- m "Full input JSON: %s" (Test_json_utils.to_string input_json));
5151-5252- (* Show input details *)
5353- (* Try to extract key information from the input *)
5454- (try
5555- match tool_name with
5656- | "Read" -> (
5757- match Test_json_utils.get_string input_json "file_path" with
5858- | Some file_path -> Log.app (fun m -> m "File: %s" file_path)
5959- | None -> ())
6060- | "Bash" -> (
6161- match Test_json_utils.get_string input_json "command" with
6262- | Some command -> Log.app (fun m -> m "Command: %s" command)
6363- | None -> ())
6464- | "Write" | "Edit" -> (
6565- match Test_json_utils.get_string input_json "file_path" with
6666- | Some file_path -> Log.app (fun m -> m "File: %s" file_path)
6767- | None -> ())
6868- | "Glob" -> (
6969- match Test_json_utils.get_string input_json "pattern" with
7070- | Some pattern -> (
7171- Log.app (fun m -> m "Pattern: %s" pattern);
7272- match Test_json_utils.get_string input_json "path" with
7373- | Some path -> Log.app (fun m -> m "Path: %s" path)
7474- | None -> Log.app (fun m -> m "Path: (current directory)"))
7575- | None -> ())
7676- | "Grep" -> (
7777- match Test_json_utils.get_string input_json "pattern" with
7878- | Some pattern -> (
7979- Log.app (fun m -> m "Pattern: %s" pattern);
8080- match Test_json_utils.get_string input_json "path" with
8181- | Some path -> Log.app (fun m -> m "Path: %s" path)
8282- | None -> Log.app (fun m -> m "Path: (current directory)"))
8383- | None -> ())
8484- | _ ->
8585- Log.app (fun m -> m "Input: %s" (Test_json_utils.to_string input_json))
8686- with exn ->
8787- Log.info (fun m ->
8888- m "Failed to parse input details: %s" (Printexc.to_string exn)));
8989-9090- (* Check if already granted *)
9191- if Granted.is_granted tool_name then begin
9292- Log.app (fun m -> m "→ Auto-approved (previously granted)");
9393- Log.info (fun m -> m "Returning allow result for %s" tool_name);
9494- Decision.allow ()
9595- end
9696- else begin
9797- (* Ask user - read from /dev/tty since stdin is connected to Claude process *)
9898- Printf.printf "Allow? [y/N/always]: %!";
9999- let tty = open_in "/dev/tty" in
100100- let response = input_line tty |> String.lowercase_ascii in
101101- close_in tty;
102102- match response with
103103- | "y" | "yes" ->
104104- Log.app (fun m -> m "→ Allowed (this time only)");
105105- Log.info (fun m -> m "User approved %s for this request only" tool_name);
106106- Decision.allow ()
107107- | "a" | "always" ->
108108- Granted.grant tool_name;
109109- Log.info (fun m ->
110110- m "User granted permanent permission for %s" tool_name);
111111- Decision.allow ()
112112- | _ ->
113113- Granted.deny tool_name;
114114- Log.info (fun m -> m "User denied permission for %s" tool_name);
115115- Decision.deny
116116- ~message:(Printf.sprintf "User denied access to %s" tool_name)
117117- ~interrupt:false
118118- end
119119-120120-let process_response client =
121121- let responses = Claude.Client.receive_all client in
122122- List.iter
123123- (fun response ->
124124- match response with
125125- | Claude.Response.Text t ->
126126- let text = Claude.Response.Text.content t in
127127- Log.app (fun m -> m "\n📝 Claude says:\n%s" text)
128128- | Claude.Response.Tool_use t ->
129129- Log.info (fun m ->
130130- m "🔧 Tool use: %s (id: %s)"
131131- (Claude.Response.Tool_use.name t)
132132- (Claude.Response.Tool_use.id t))
133133- | Claude.Response.Complete c ->
134134- (if Claude.Response.Complete.result_text c = None then
135135- Log.err (fun m -> m "❌ Error occurred!")
136136- else
137137- match Claude.Response.Complete.total_cost_usd c with
138138- | Some cost -> Log.info (fun m -> m "💰 Cost: $%.6f" cost)
139139- | None -> ());
140140- Log.info (fun m ->
141141- m "⏱️ Duration: %dms" (Claude.Response.Complete.duration_ms c))
142142- | Claude.Response.Error e ->
143143- Log.err (fun m -> m "❌ Error: %s" (Claude.Response.Error.message e))
144144- | _ -> ())
145145- responses
146146-147147-let run_demo ~sw ~env =
148148- Log.app (fun m -> m "🚀 Starting Permission Demo");
149149- Log.app (fun m -> m "==================================");
150150- Log.app (fun m -> m "This demo starts with NO permissions.");
151151- Log.app (fun m -> m "Claude will request permissions as needed.\n");
152152-153153- (* Create options with custom permission callback *)
154154- (* DON'T specify allowed_tools - let the permission callback handle everything.
155155- The Default permission mode with a callback should send requests for all tools. *)
156156- let options =
157157- Claude.Options.default
158158- |> Claude.Options.with_model (Claude.Model.of_string "sonnet")
159159- |> Claude.Options.with_permission_mode Claude.Permissions.Mode.Default
160160- |> Claude.Options.with_permission_callback interactive_permission_callback
161161- in
162162-163163- let client =
164164- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
165165- ~clock:env#clock ()
166166- in
167167-168168- (* First prompt - Claude will need to request Read permission for ../lib *)
169169- Log.app (fun m -> m "\n📤 Sending first prompt (reading from ../lib)...");
170170- Claude.Client.query client
171171- "Please read and analyze the source files in the ../lib directory. Focus \
172172- on the main OCaml modules and their purpose. What is the overall \
173173- architecture of this Claude library?";
174174- process_response client;
175175-176176- (* Show current permissions *)
177177- Log.app (fun m -> m "\n📋 Current permission status:");
178178- Granted.list ();
179179-180180- (* Second prompt - will need Write permission *)
181181- Log.app (fun m -> m "\n📤 Sending second prompt (writing TEST.md)...");
182182- Claude.Client.query client
183183- "Now write a summary of what you learned about the Claude library \
184184- architecture to a file called TEST.md in the current directory. Include \
185185- the main modules, their purposes, and how they work together.";
186186- process_response client;
187187-188188- (* Show final permissions *)
189189- Log.app (fun m -> m "\n📋 Final permission status:");
190190- Granted.list ();
191191-192192- Log.app (fun m -> m "\n==================================");
193193- Log.app (fun m -> m "✨ Demo complete!")
194194-195195-let main ~env = Switch.run @@ fun sw -> run_demo ~sw ~env
196196-197197-(* Command-line interface *)
198198-open Cmdliner
199199-200200-let main_term env =
201201- let setup_log style_renderer level =
202202- Fmt_tty.setup_std_outputs ?style_renderer ();
203203- Logs.set_level level;
204204- Logs.set_reporter (Logs_fmt.reporter ());
205205- (* Set default to App level if not specified *)
206206- if level = None then Logs.set_level (Some Logs.App);
207207- (* Enable info level for Client module if in info mode or above *)
208208- match level with
209209- | Some Logs.Info | Some Logs.Debug ->
210210- Logs.Src.set_level Claude.Client.src (Some Logs.Info)
211211- | _ -> ()
212212- in
213213- let run style level =
214214- setup_log style level;
215215- main ~env
216216- in
217217- Term.(const run $ Fmt_cli.style_renderer () $ Logs_cli.level ())
218218-219219-let cmd env =
220220- let doc = "Demonstrate Claude's dynamic permission system" in
221221- let man =
222222- [
223223- `S Manpage.s_description;
224224- `P
225225- "This program demonstrates how to use permission callbacks with Claude.";
226226- `P "It starts with no permissions and asks for them interactively.";
227227- `P "You can grant permissions for:";
228228- `P "- Individual requests (y/yes)";
229229- `P "- All future requests of that type (a/always)";
230230- `P "- Or deny the request (n/no or just press Enter)";
231231- `S Manpage.s_examples;
232232- `P "Run the demo:";
233233- `Pre " $(mname)";
234234- `P "Run with verbose output to see message flow:";
235235- `Pre " $(mname) -v";
236236- `S Manpage.s_bugs;
237237- `P "Report bugs at https://github.com/your-repo/issues";
238238- ]
239239- in
240240- let info = Cmd.info "permission_demo" ~version:"1.0" ~doc ~man in
241241- Cmd.v info (main_term env)
242242-243243-let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))
-185
test/permission_demo.py
···11-#!/usr/bin/env python3
22-# /// script
33-# requires-python = ">=3.9"
44-# dependencies = [
55-# "claude-code-sdk",
66-# ]
77-# ///
88-"""
99-Permission demo for Claude Code SDK Python.
1010-Demonstrates how the permission callback system works.
1111-"""
1212-1313-import asyncio
1414-import sys
1515-import logging
1616-from typing import Any, Dict
1717-1818-from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
1919-from claude_code_sdk.types import (
2020- PermissionResultAllow,
2121- PermissionResultDeny,
2222- ToolPermissionContext,
2323-)
2424-2525-# Set up logging
2626-logging.basicConfig(
2727- level=logging.INFO,
2828- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
2929-)
3030-logger = logging.getLogger(__name__)
3131-3232-# Track granted permissions
3333-granted_permissions = set()
3434-3535-3636-async def interactive_permission_callback(
3737- tool_name: str,
3838- tool_input: Dict[str, Any],
3939- context: ToolPermissionContext
4040-) -> PermissionResultAllow | PermissionResultDeny:
4141- """Interactive permission callback that asks user for permission."""
4242-4343- logger.info(f"🔔 Permission callback invoked for tool: {tool_name}")
4444- print(f"\n🔐 PERMISSION REQUEST 🔐")
4545- print(f"Tool: {tool_name}")
4646-4747- # Log the full input for debugging
4848- logger.info(f"Full input: {tool_input}")
4949-5050- # Show input details
5151- try:
5252- if tool_name == "Read":
5353- file_path = tool_input.get("file_path", "")
5454- print(f"File: {file_path}")
5555- elif tool_name == "Bash":
5656- command = tool_input.get("command", "")
5757- print(f"Command: {command}")
5858- elif tool_name in ["Write", "Edit"]:
5959- file_path = tool_input.get("file_path", "")
6060- print(f"File: {file_path}")
6161- elif tool_name == "Glob":
6262- pattern = tool_input.get("pattern", "")
6363- path = tool_input.get("path", "(current directory)")
6464- print(f"Pattern: {pattern}")
6565- print(f"Path: {path}")
6666- elif tool_name == "Grep":
6767- pattern = tool_input.get("pattern", "")
6868- path = tool_input.get("path", "(current directory)")
6969- print(f"Pattern: {pattern}")
7070- print(f"Path: {path}")
7171- else:
7272- print(f"Input: {tool_input}")
7373- except Exception as e:
7474- logger.info(f"Failed to parse input details: {e}")
7575-7676- # Check if already granted
7777- if tool_name in granted_permissions:
7878- print("→ Auto-approved (previously granted)")
7979- logger.info(f"Returning allow result for {tool_name}")
8080- return PermissionResultAllow()
8181-8282- # Ask user
8383- response = input("Allow? [y/N/always]: ").lower().strip()
8484-8585- if response in ["y", "yes"]:
8686- print("→ Allowed (this time only)")
8787- logger.info(f"User approved {tool_name} for this request only")
8888- return PermissionResultAllow()
8989- elif response in ["a", "always"]:
9090- granted_permissions.add(tool_name)
9191- print(f"✅ Permission granted for: {tool_name}")
9292- logger.info(f"User granted permanent permission for {tool_name}")
9393- return PermissionResultAllow()
9494- else:
9595- print(f"❌ Permission denied for: {tool_name}")
9696- logger.info(f"User denied permission for {tool_name}")
9797- return PermissionResultDeny(
9898- message=f"User denied access to {tool_name}",
9999- interrupt=False
100100- )
101101-102102-103103-async def run_demo():
104104- """Run the permission demo."""
105105- print("🚀 Starting Permission Demo")
106106- print("==================================")
107107- print("This demo starts with NO permissions.")
108108- print("Claude will request permissions as needed.\n")
109109-110110- # Create options with custom permission callback
111111- # Test WITHOUT allowed_tools to see if permission requests come through
112112- options = ClaudeCodeOptions(
113113- model="sonnet",
114114- # allowed_tools=["Read", "Write", "Bash", "Edit", "Glob", "Grep"],
115115- can_use_tool=interactive_permission_callback,
116116- )
117117-118118- async with ClaudeSDKClient(options=options) as client:
119119- # First prompt - Claude will need to request Read permission
120120- print("\n📤 Sending first prompt (reading from ../lib)...")
121121- messages = []
122122- await client.query(
123123- "Please read and analyze the source files in the ../lib directory. "
124124- "Focus on the main OCaml modules and their purpose. "
125125- "What is the overall architecture of this Claude library?"
126126- )
127127-128128- async for msg in client.receive_response():
129129- messages.append(msg)
130130- if hasattr(msg, 'content'):
131131- if isinstance(msg.content, str):
132132- print(f"\n📝 Claude says:\n{msg.content}")
133133- elif isinstance(msg.content, list):
134134- for block in msg.content:
135135- if hasattr(block, 'text'):
136136- print(f"\n📝 Claude says:\n{block.text}")
137137-138138- # Show current permissions
139139- print("\n📋 Current permission status:")
140140- if granted_permissions:
141141- print(f"Currently granted permissions: {', '.join(granted_permissions)}")
142142- else:
143143- print("No permissions granted yet")
144144-145145- # Second prompt - will need Write permission
146146- print("\n📤 Sending second prompt (writing TEST.md)...")
147147- await client.query(
148148- "Now write a summary of what you learned about the Claude library "
149149- "architecture to a file called TEST.md in the current directory. "
150150- "Include the main modules, their purposes, and how they work together."
151151- )
152152-153153- async for msg in client.receive_response():
154154- if hasattr(msg, 'content'):
155155- if isinstance(msg.content, str):
156156- print(f"\n📝 Claude says:\n{msg.content}")
157157- elif isinstance(msg.content, list):
158158- for block in msg.content:
159159- if hasattr(block, 'text'):
160160- print(f"\n📝 Claude says:\n{block.text}")
161161-162162- # Show final permissions
163163- print("\n📋 Final permission status:")
164164- if granted_permissions:
165165- print(f"Currently granted permissions: {', '.join(granted_permissions)}")
166166- else:
167167- print("No permissions granted yet")
168168-169169- print("\n==================================")
170170- print("✨ Demo complete!")
171171-172172-173173-async def main():
174174- """Main entry point."""
175175- try:
176176- await run_demo()
177177- except KeyboardInterrupt:
178178- print("\n\nDemo interrupted by user.")
179179- except Exception as e:
180180- logger.error(f"Error in demo: {e}", exc_info=True)
181181- sys.exit(1)
182182-183183-184184-if __name__ == "__main__":
185185- asyncio.run(main())
-3
test/secret_data.txt
···11-The secret code is: OCAML-2024-ROCKS
22-This file was created specifically for the permission demo.
33-Claude should not know about this content without reading the file.
-137
test/simple_permission_test.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-open Eio.Std
77-88-let src = Logs.Src.create "simple_permission_test" ~doc:"Simple permission test"
99-1010-module Log = (val Logs.src_log src : Logs.LOG)
1111-1212-(* Auto-allow callback that logs what it sees *)
1313-let auto_allow_callback ctx =
1414- Log.app (fun m -> m "\n🔐 Permission callback invoked!");
1515- Log.app (fun m -> m " Tool: %s" ctx.Claude.Permissions.tool_name);
1616- Log.app (fun m ->
1717- m " Input: %s"
1818- (Test_json_utils.to_string
1919- (Claude.Tool_input.to_json ctx.Claude.Permissions.input)));
2020- Log.app (fun m -> m " ✅ Auto-allowing");
2121- Claude.Permissions.Decision.allow ()
2222-2323-let run_test ~sw ~env =
2424- Log.app (fun m -> m "🧪 Testing Permission Callbacks (Auto-Allow Mode)");
2525- Log.app (fun m -> m "====================================================");
2626-2727- (* Create options with permission callback *)
2828- let options =
2929- Claude.Options.default
3030- |> Claude.Options.with_model (Claude.Model.of_string "sonnet")
3131- |> Claude.Options.with_permission_callback auto_allow_callback
3232- in
3333-3434- Log.app (fun m -> m "Creating client with permission callback...");
3535- let client =
3636- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
3737- ~clock:env#clock ()
3838- in
3939-4040- (* Query that should trigger Write tool *)
4141- Log.app (fun m -> m "\n📤 Asking Claude to write a file...");
4242- Claude.Client.query client
4343- "Write a simple hello world message to /tmp/test_permission.txt";
4444-4545- (* Process response *)
4646- let messages = Claude.Client.receive_all client in
4747- Log.app (fun m -> m "\n📨 Received %d messages" (List.length messages));
4848-4949- let tool_count = ref 0 in
5050- let write_used = ref false in
5151-5252- List.iter
5353- (fun resp ->
5454- match resp with
5555- | Claude.Response.Text text ->
5656- let content = Claude.Response.Text.content text in
5757- if String.length content > 0 then
5858- Log.app (fun m -> m "\n💬 Claude: %s" content)
5959- | Claude.Response.Tool_use t ->
6060- incr tool_count;
6161- let tool_name = Claude.Response.Tool_use.name t in
6262- if tool_name = "Write" then write_used := true;
6363- Log.app (fun m -> m "🔧 Tool use #%d: %s" !tool_count tool_name)
6464- | Claude.Response.Tool_result r ->
6565- let tool_use_id = Claude.Content_block.Tool_result.tool_use_id r in
6666- let is_error =
6767- Claude.Content_block.Tool_result.is_error r
6868- |> Option.value ~default:false
6969- in
7070- if is_error then begin
7171- Log.app (fun m -> m "\n⚠️ Tool result error for %s:" tool_use_id);
7272- match Claude.Content_block.Tool_result.content r with
7373- | Some json ->
7474- let s =
7575- match Jsont_bytesrw.encode_string' Jsont.json json with
7676- | Ok str -> str
7777- | Error _ -> "<encoding error>"
7878- in
7979- Log.app (fun m -> m " %s" s)
8080- | None -> ()
8181- end
8282- | Claude.Response.Complete result ->
8383- Log.app (fun m -> m "\n✅ Success!");
8484- (match Claude.Response.Complete.total_cost_usd result with
8585- | Some cost -> Log.app (fun m -> m "💰 Cost: $%.6f" cost)
8686- | None -> ());
8787- Log.app (fun m ->
8888- m "⏱️ Duration: %dms"
8989- (Claude.Response.Complete.duration_ms result))
9090- | Claude.Response.Error err ->
9191- Log.err (fun m ->
9292- m "\n❌ Error: %s" (Claude.Response.Error.message err))
9393- | _ -> ())
9494- messages;
9595-9696- Log.app (fun m -> m "\n====================================================");
9797- Log.app (fun m -> m "📊 Test Results:");
9898- Log.app (fun m -> m " Total tools used: %d" !tool_count);
9999- Log.app (fun m -> m " Write tool used: %b" !write_used);
100100-101101- if !write_used then
102102- Log.app (fun m ->
103103- m " ✅ Permission callback successfully intercepted Write tool!")
104104- else Log.app (fun m -> m " ⚠️ Write tool was not used (unexpected)");
105105-106106- Log.app (fun m -> m "====================================================");
107107- Log.app (fun m -> m "✨ Test complete!")
108108-109109-let main ~env = Switch.run @@ fun sw -> run_test ~sw ~env
110110-111111-(* Command-line interface *)
112112-open Cmdliner
113113-114114-let main_term env =
115115- let setup_log style_renderer level =
116116- Fmt_tty.setup_std_outputs ?style_renderer ();
117117- Logs.set_level level;
118118- Logs.set_reporter (Logs_fmt.reporter ());
119119- if level = None then Logs.set_level (Some Logs.App);
120120- match level with
121121- | Some Logs.Info | Some Logs.Debug ->
122122- Logs.Src.set_level Claude.Client.src (Some Logs.Info);
123123- Logs.Src.set_level Claude.Transport.src (Some Logs.Info)
124124- | _ -> ()
125125- in
126126- let run style level =
127127- setup_log style level;
128128- main ~env
129129- in
130130- Term.(const run $ Fmt_cli.style_renderer () $ Logs_cli.level ())
131131-132132-let cmd env =
133133- let doc = "Test permission callback with auto-allow" in
134134- let info = Cmd.info "simple_permission_test" ~version:"1.0" ~doc in
135135- Cmd.v info (main_term env)
136136-137137-let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))
-228
test/simulated_permissions.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-let src =
77- Logs.Src.create "simulated_permissions"
88- ~doc:"Simulated permission demonstration"
99-1010-module Log = (val Logs.src_log src : Logs.LOG)
1111-1212-(* Track granted permissions *)
1313-module PermissionState = struct
1414- module StringSet = Set.Make (String)
1515-1616- let granted = ref StringSet.empty
1717- let denied = ref StringSet.empty
1818-1919- let grant tool =
2020- granted := StringSet.add tool !granted;
2121- denied := StringSet.remove tool !denied
2222-2323- let deny tool =
2424- denied := StringSet.add tool !denied;
2525- granted := StringSet.remove tool !granted
2626-2727- let is_granted tool = StringSet.mem tool !granted
2828- let is_denied tool = StringSet.mem tool !denied
2929-3030- let _reset () =
3131- granted := StringSet.empty;
3232- denied := StringSet.empty
3333-3434- let show () =
3535- Log.app (fun m -> m "\n📊 Permission Status:");
3636- if StringSet.is_empty !granted && StringSet.is_empty !denied then
3737- Log.app (fun m -> m " No permissions configured")
3838- else begin
3939- if not (StringSet.is_empty !granted) then
4040- Log.app (fun m ->
4141- m " ✅ Granted: %s"
4242- (StringSet.elements !granted |> String.concat ", "));
4343- if not (StringSet.is_empty !denied) then
4444- Log.app (fun m ->
4545- m " ❌ Denied: %s" (StringSet.elements !denied |> String.concat ", "))
4646- end
4747-end
4848-4949-(* Example permission callback *)
5050-let example_permission_callback ctx =
5151- let open Claude.Permissions in
5252- let tool_name = ctx.tool_name in
5353-5454- Log.app (fun m -> m "\n🔐 Permission Request for: %s" tool_name);
5555-5656- (* Check current state *)
5757- if PermissionState.is_granted tool_name then begin
5858- Log.app (fun m -> m " → Auto-approved (previously granted)");
5959- Decision.allow ()
6060- end
6161- else if PermissionState.is_denied tool_name then begin
6262- Log.app (fun m -> m " → Auto-denied (previously denied)");
6363- Decision.deny
6464- ~message:(Printf.sprintf "Tool %s is blocked by policy" tool_name)
6565- ~interrupt:false
6666- end
6767- else begin
6868- (* Ask user *)
6969- Printf.printf " Allow %s? [y/n/always/never]: %!" tool_name;
7070- match read_line () |> String.lowercase_ascii with
7171- | "y" | "yes" ->
7272- Log.app (fun m -> m " → Allowed (one time)");
7373- Decision.allow ()
7474- | "n" | "no" ->
7575- Log.app (fun m -> m " → Denied (one time)");
7676- Decision.deny
7777- ~message:(Printf.sprintf "User denied %s" tool_name)
7878- ~interrupt:false
7979- | "a" | "always" ->
8080- PermissionState.grant tool_name;
8181- Log.app (fun m -> m " → Allowed (always)");
8282- Decision.allow ()
8383- | "never" ->
8484- PermissionState.deny tool_name;
8585- Log.app (fun m -> m " → Denied (always)");
8686- Decision.deny
8787- ~message:(Printf.sprintf "Tool %s permanently blocked" tool_name)
8888- ~interrupt:false
8989- | _ ->
9090- Log.app (fun m -> m " → Denied (invalid response)");
9191- Decision.deny ~message:"Invalid permission response" ~interrupt:false
9292- end
9393-9494-(* Demonstrate the permission system *)
9595-let demo_permissions () =
9696- Log.app (fun m -> m "🎭 Permission System Demonstration");
9797- Log.app (fun m -> m "==================================\n");
9898-9999- (* Simulate permission requests *)
100100- let tools = [ "Read"; "Write"; "Bash"; "Edit" ] in
101101-102102- Log.app (fun m -> m "This demo simulates permission requests.");
103103- Log.app (fun m -> m "You can respond with: y/n/always/never\n");
104104-105105- (* Test each tool *)
106106- List.iter
107107- (fun tool_name ->
108108- let input =
109109- let open Jsont in
110110- Object
111111- ( [
112112- (("file_path", Meta.none), String ("/example/path.txt", Meta.none));
113113- ],
114114- Meta.none )
115115- in
116116- let tool_input = Claude.Tool_input.of_json input in
117117- let ctx =
118118- Claude.Permissions.
119119- { tool_name; input = tool_input; suggested_rules = [] }
120120- in
121121- let decision = example_permission_callback ctx in
122122-123123- (* Show result *)
124124- if Claude.Permissions.Decision.is_allow decision then
125125- Log.info (fun m -> m "Result: Permission granted for %s" tool_name)
126126- else
127127- match Claude.Permissions.Decision.deny_message decision with
128128- | Some message ->
129129- Log.info (fun m ->
130130- m "Result: Permission denied for %s - %s" tool_name message)
131131- | None ->
132132- Log.info (fun m -> m "Result: Permission denied for %s" tool_name))
133133- tools;
134134-135135- (* Show final state *)
136136- PermissionState.show ()
137137-138138-(* Also demonstrate discovery callback *)
139139-let demo_discovery () =
140140- Log.app (fun m -> m "\n\n🔍 Discovery Callback Demonstration");
141141- Log.app (fun m -> m "====================================\n");
142142-143143- let discovered = ref [] in
144144- let callback = Claude.Permissions.discovery discovered in
145145-146146- (* Simulate some tool requests *)
147147- let requests =
148148- let open Jsont in
149149- [
150150- ( "Read",
151151- Object
152152- ( [ (("file_path", Meta.none), String ("test.ml", Meta.none)) ],
153153- Meta.none ) );
154154- ( "Bash",
155155- Object
156156- ([ (("command", Meta.none), String ("ls -la", Meta.none)) ], Meta.none)
157157- );
158158- ( "Write",
159159- Object
160160- ( [ (("file_path", Meta.none), String ("output.txt", Meta.none)) ],
161161- Meta.none ) );
162162- ]
163163- in
164164-165165- Log.app (fun m -> m "Simulating tool requests with discovery callback...\n");
166166-167167- List.iter
168168- (fun (tool_name, input) ->
169169- Log.app (fun m -> m " Request: %s" tool_name);
170170- let tool_input = Claude.Tool_input.of_json input in
171171- let ctx =
172172- Claude.Permissions.
173173- { tool_name; input = tool_input; suggested_rules = [] }
174174- in
175175- let _ = callback ctx in
176176- ())
177177- requests;
178178-179179- Log.app (fun m -> m "\n📋 Discovered permissions:");
180180- if !discovered = [] then Log.app (fun m -> m " None")
181181- else
182182- List.iter
183183- (fun rule ->
184184- Log.app (fun m ->
185185- m " - %s%s"
186186- (Claude.Permissions.Rule.tool_name rule)
187187- (match Claude.Permissions.Rule.rule_content rule with
188188- | Some content -> Printf.sprintf " (content: %s)" content
189189- | None -> "")))
190190- !discovered
191191-192192-let main () =
193193- demo_permissions ();
194194- demo_discovery ()
195195-196196-(* Command-line interface *)
197197-open Cmdliner
198198-199199-let main_term =
200200- let setup_log style_renderer level =
201201- Fmt_tty.setup_std_outputs ?style_renderer ();
202202- Logs.set_level level;
203203- Logs.set_reporter (Logs_fmt.reporter ());
204204- if level = None then Logs.set_level (Some Logs.App)
205205- in
206206- let run style level =
207207- setup_log style level;
208208- main ()
209209- in
210210- Term.(const run $ Fmt_cli.style_renderer () $ Logs_cli.level ())
211211-212212-let cmd =
213213- let doc = "Demonstrate permission callbacks and discovery" in
214214- let man =
215215- [
216216- `S Manpage.s_description;
217217- `P
218218- "This program demonstrates how permission callbacks work in the Claude \
219219- OCaml library.";
220220- `P
221221- "It simulates permission requests and shows how to implement custom \
222222- callbacks.";
223223- ]
224224- in
225225- let info = Cmd.info "simulated_permissions" ~version:"1.0" ~doc ~man in
226226- Cmd.v info main_term
227227-228228-let () = exit (Cmd.eval cmd)
-228
test/structured_output_demo.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(* Example demonstrating structured output with JSON Schema *)
77-88-module C = Claude
99-1010-let () =
1111- (* Configure logging to see what's happening *)
1212- Logs.set_reporter (Logs_fmt.reporter ());
1313- Logs.set_level (Some Logs.Info);
1414- Logs.Src.set_level C.Message.src (Some Logs.Debug)
1515-1616-let run_codebase_analysis env =
1717- Printf.printf "\n=== Codebase Analysis with Structured Output ===\n\n";
1818-1919- (* Define the JSON Schema for our expected output structure *)
2020- let analysis_schema =
2121- let open Jsont in
2222- Object
2323- ( [
2424- (("type", Meta.none), String ("object", Meta.none));
2525- ( ("properties", Meta.none),
2626- Object
2727- ( [
2828- ( ("file_count", Meta.none),
2929- Object
3030- ( [
3131- (("type", Meta.none), String ("integer", Meta.none));
3232- ( ("description", Meta.none),
3333- String ("Total number of files analyzed", Meta.none)
3434- );
3535- ],
3636- Meta.none ) );
3737- ( ("has_tests", Meta.none),
3838- Object
3939- ( [
4040- (("type", Meta.none), String ("boolean", Meta.none));
4141- ( ("description", Meta.none),
4242- String
4343- ("Whether the codebase has test files", Meta.none)
4444- );
4545- ],
4646- Meta.none ) );
4747- ( ("primary_language", Meta.none),
4848- Object
4949- ( [
5050- (("type", Meta.none), String ("string", Meta.none));
5151- ( ("description", Meta.none),
5252- String
5353- ( "The primary programming language used",
5454- Meta.none ) );
5555- ],
5656- Meta.none ) );
5757- ( ("complexity_rating", Meta.none),
5858- Object
5959- ( [
6060- (("type", Meta.none), String ("string", Meta.none));
6161- ( ("enum", Meta.none),
6262- Array
6363- ( [
6464- String ("low", Meta.none);
6565- String ("medium", Meta.none);
6666- String ("high", Meta.none);
6767- ],
6868- Meta.none ) );
6969- ( ("description", Meta.none),
7070- String ("Overall complexity rating", Meta.none) );
7171- ],
7272- Meta.none ) );
7373- ( ("key_findings", Meta.none),
7474- Object
7575- ( [
7676- (("type", Meta.none), String ("array", Meta.none));
7777- ( ("items", Meta.none),
7878- Object
7979- ( [
8080- ( ("type", Meta.none),
8181- String ("string", Meta.none) );
8282- ],
8383- Meta.none ) );
8484- ( ("description", Meta.none),
8585- String
8686- ( "List of key findings from the analysis",
8787- Meta.none ) );
8888- ],
8989- Meta.none ) );
9090- ],
9191- Meta.none ) );
9292- ( ("required", Meta.none),
9393- Array
9494- ( [
9595- String ("file_count", Meta.none);
9696- String ("has_tests", Meta.none);
9797- String ("primary_language", Meta.none);
9898- String ("complexity_rating", Meta.none);
9999- String ("key_findings", Meta.none);
100100- ],
101101- Meta.none ) );
102102- (("additionalProperties", Meta.none), Bool (false, Meta.none));
103103- ],
104104- Meta.none )
105105- in
106106-107107- (* Create structured output format from the schema *)
108108- let output_format =
109109- Claude.Proto.Structured_output.of_json_schema analysis_schema
110110- in
111111-112112- (* Configure Claude with structured output *)
113113- let options =
114114- C.Options.default
115115- |> C.Options.with_output_format output_format
116116- |> C.Options.with_allowed_tools [ "Read"; "Glob"; "Grep" ]
117117- |> C.Options.with_system_prompt
118118- "You are a code analysis assistant. Analyze codebases and provide \
119119- structured output matching the given JSON Schema."
120120- in
121121-122122- Printf.printf "Structured output format configured\n";
123123- Printf.printf "Schema: %s\n\n"
124124- (Test_json_utils.to_string ~minify:false analysis_schema);
125125-126126- (* Create Claude client and query *)
127127- Eio.Switch.run @@ fun sw ->
128128- let process_mgr = Eio.Stdenv.process_mgr env in
129129- let clock = Eio.Stdenv.clock env in
130130- let client = C.Client.create ~sw ~process_mgr ~clock ~options () in
131131-132132- let prompt =
133133- "Please analyze the current codebase structure. Look at the files, \
134134- identify the primary language, count files, check for tests, assess \
135135- complexity, and provide key findings. Return your analysis in the \
136136- structured JSON format I specified."
137137- in
138138-139139- Printf.printf "Sending query: %s\n\n" prompt;
140140- C.Client.query client prompt;
141141-142142- (* Process responses *)
143143- let responses = C.Client.receive client in
144144- Seq.iter
145145- (function
146146- | C.Response.Text text ->
147147- Printf.printf "\nAssistant text:\n";
148148- Printf.printf " %s\n" (C.Response.Text.content text)
149149- | C.Response.Tool_use tool ->
150150- Printf.printf " Using tool: %s\n" (C.Response.Tool_use.name tool)
151151- | C.Response.Complete result -> (
152152- Printf.printf "\n=== Result ===\n";
153153- Printf.printf "Duration: %dms\n"
154154- (C.Response.Complete.duration_ms result);
155155- Printf.printf "Cost: $%.4f\n"
156156- (Option.value
157157- (C.Response.Complete.total_cost_usd result)
158158- ~default:0.0);
159159-160160- (* Extract and display structured output *)
161161- match C.Response.Complete.structured_output result with
162162- | Some output ->
163163- Printf.printf "\n=== Structured Output ===\n";
164164- Printf.printf "%s\n\n"
165165- (Test_json_utils.to_string ~minify:false output);
166166-167167- (* Parse the structured output *)
168168- let file_count =
169169- Test_json_utils.get_int output "file_count"
170170- |> Option.value ~default:0
171171- in
172172- let has_tests =
173173- Test_json_utils.get_bool output "has_tests"
174174- |> Option.value ~default:false
175175- in
176176- let language =
177177- Test_json_utils.get_string output "primary_language"
178178- |> Option.value ~default:"unknown"
179179- in
180180- let complexity =
181181- Test_json_utils.get_string output "complexity_rating"
182182- |> Option.value ~default:"unknown"
183183- in
184184- let findings =
185185- match Test_json_utils.get_array output "key_findings" with
186186- | Some items ->
187187- List.filter_map
188188- (fun json -> Test_json_utils.as_string json)
189189- items
190190- | None -> []
191191- in
192192-193193- Printf.printf "=== Parsed Analysis ===\n";
194194- Printf.printf "File Count: %d\n" file_count;
195195- Printf.printf "Has Tests: %b\n" has_tests;
196196- Printf.printf "Primary Language: %s\n" language;
197197- Printf.printf "Complexity: %s\n" complexity;
198198- Printf.printf "Key Findings:\n";
199199- List.iter
200200- (fun finding -> Printf.printf " - %s\n" finding)
201201- findings
202202- | None -> (
203203- Printf.printf "No structured output received\n";
204204- match C.Response.Complete.result_text result with
205205- | Some text -> Printf.printf "Text result: %s\n" text
206206- | None -> ()))
207207- | C.Response.Init _ -> Printf.printf "Session initialized\n"
208208- | C.Response.Error err ->
209209- Printf.printf "Error: %s\n" (C.Response.Error.message err)
210210- | _ -> ())
211211- responses;
212212-213213- Printf.printf "\nDone!\n"
214214-215215-let () =
216216- Eio_main.run @@ fun env ->
217217- try run_codebase_analysis env with
218218- | C.Transport.CLI_not_found msg ->
219219- Printf.eprintf "Error: Claude CLI not found\n%s\n" msg;
220220- Printf.eprintf "Make sure 'claude' is installed and in your PATH\n";
221221- exit 1
222222- | C.Transport.Connection_error msg ->
223223- Printf.eprintf "Connection error: %s\n" msg;
224224- exit 1
225225- | exn ->
226226- Printf.eprintf "Unexpected error: %s\n" (Printexc.to_string exn);
227227- Printexc.print_backtrace stderr;
228228- exit 1
-93
test/structured_output_simple.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(* Simple example showing structured output with explicit JSON Schema *)
77-88-module C = Claude
99-1010-let () =
1111- Logs.set_reporter (Logs_fmt.reporter ());
1212- Logs.set_level (Some Logs.Info)
1313-1414-let simple_example env =
1515- Printf.printf "\n=== Simple Structured Output Example ===\n\n";
1616-1717- (* Define a simple schema for a person's info *)
1818- let person_schema =
1919- let open Jsont in
2020- Object
2121- ( [
2222- (("type", Meta.none), String ("object", Meta.none));
2323- ( ("properties", Meta.none),
2424- Object
2525- ( [
2626- ( ("name", Meta.none),
2727- Object
2828- ( [ (("type", Meta.none), String ("string", Meta.none)) ],
2929- Meta.none ) );
3030- ( ("age", Meta.none),
3131- Object
3232- ( [ (("type", Meta.none), String ("integer", Meta.none)) ],
3333- Meta.none ) );
3434- ( ("occupation", Meta.none),
3535- Object
3636- ( [ (("type", Meta.none), String ("string", Meta.none)) ],
3737- Meta.none ) );
3838- ],
3939- Meta.none ) );
4040- ( ("required", Meta.none),
4141- Array
4242- ( [
4343- String ("name", Meta.none);
4444- String ("age", Meta.none);
4545- String ("occupation", Meta.none);
4646- ],
4747- Meta.none ) );
4848- ],
4949- Meta.none )
5050- in
5151-5252- let output_format =
5353- Claude.Proto.Structured_output.of_json_schema person_schema
5454- in
5555-5656- let options =
5757- C.Options.default
5858- |> C.Options.with_output_format output_format
5959- |> C.Options.with_max_turns 1
6060- in
6161-6262- Printf.printf "Asking Claude to provide structured data...\n\n";
6363-6464- Eio.Switch.run @@ fun sw ->
6565- let process_mgr = Eio.Stdenv.process_mgr env in
6666- let clock = Eio.Stdenv.clock env in
6767- let client = C.Client.create ~sw ~process_mgr ~clock ~options () in
6868-6969- C.Client.query client
7070- "Tell me about a famous computer scientist. Provide their name, age, and \
7171- occupation in the exact JSON structure I specified.";
7272-7373- let responses = C.Client.receive_all client in
7474- List.iter
7575- (function
7676- | C.Response.Complete result -> (
7777- Printf.printf "Response received!\n";
7878- match C.Response.Complete.structured_output result with
7979- | Some json ->
8080- Printf.printf "\nStructured Output:\n%s\n"
8181- (Test_json_utils.to_string ~minify:false json)
8282- | None -> Printf.printf "No structured output\n")
8383- | C.Response.Error err ->
8484- Printf.printf "Error: %s\n" (C.Response.Error.message err)
8585- | _ -> ())
8686- responses
8787-8888-let () =
8989- Eio_main.run @@ fun env ->
9090- try simple_example env
9191- with exn ->
9292- Printf.eprintf "Error: %s\n" (Printexc.to_string exn);
9393- exit 1
-31
test/test_json_utils.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(* Helper functions for JSON operations in tests using jsont codecs *)
77-88-let to_string ?(minify = false) json =
99- let format = if minify then Jsont.Minify else Jsont.Indent in
1010- match Jsont_bytesrw.encode_string' ~format Jsont.json json with
1111- | Ok s -> s
1212- | Error err -> Jsont.Error.to_string err
1313-1414-(* Helper to decode an optional field with a given codec *)
1515-let get_opt (type a) (codec : a Jsont.t) json key : a option =
1616- let field_codec =
1717- Jsont.Object.map ~kind:"field" (fun v -> v)
1818- |> Jsont.Object.opt_mem key codec ~enc:Fun.id
1919- |> Jsont.Object.finish
2020- in
2121- match Jsont.Json.decode field_codec json with Ok v -> v | Error _ -> None
2222-2323-let get_string json key = get_opt Jsont.string json key
2424-let get_int json key = get_opt Jsont.int json key
2525-let get_bool json key = get_opt Jsont.bool json key
2626-let get_array json key = get_opt (Jsont.list Jsont.json) json key
2727-2828-let as_string json =
2929- match Jsont.Json.decode Jsont.string json with
3030- | Ok s -> Some s
3131- | Error _ -> None
-283
test/test_structured_error.ml
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44----------------------------------------------------------------------------*)
55-66-(** Test structured errors by provoking a JSON-RPC error from Claude *)
77-88-open Eio.Std
99-1010-let test_create_error_detail () =
1111- print_endline "\nTesting structured error creation...";
1212-1313- (* Create a simple error *)
1414- let error1 =
1515- Proto.Control.Response.error_detail ~code:`Method_not_found
1616- ~message:"Method not found" ()
1717- in
1818- Printf.printf "✓ Created error: [%d] %s\n" error1.code error1.message;
1919-2020- (* Create an error without additional data for simplicity *)
2121- let error2 =
2222- Proto.Control.Response.error_detail ~code:`Invalid_params
2323- ~message:"Invalid parameters" ()
2424- in
2525- Printf.printf "✓ Created error: [%d] %s\n" error2.code error2.message;
2626-2727- (* Encode and decode an error response *)
2828- let error_resp =
2929- Proto.Control.Response.error ~request_id:"test-123" ~error:error2 ()
3030- in
3131-3232- match Jsont.Json.encode Proto.Control.Response.jsont error_resp with
3333- | Ok json -> (
3434- let json_str =
3535- match Jsont_bytesrw.encode_string' Jsont.json json with
3636- | Ok s -> s
3737- | Error e -> Jsont.Error.to_string e
3838- in
3939- Printf.printf "✓ Encoded error response: %s\n" json_str;
4040-4141- (* Decode it back *)
4242- match Jsont.Json.decode Proto.Control.Response.jsont json with
4343- | Ok (Proto.Control.Response.Error decoded) ->
4444- Printf.printf "✓ Decoded error: [%d] %s\n" decoded.error.code
4545- decoded.error.message
4646- | Ok _ -> print_endline "✗ Wrong response type"
4747- | Error e -> Printf.printf "✗ Decode failed: %s\n" e)
4848- | Error e -> Printf.printf "✗ Encode failed: %s\n" e
4949-5050-let test_error_code_conventions () =
5151- print_endline "\nTesting JSON-RPC error code conventions...";
5252-5353- (* Standard JSON-RPC errors using the typed API with polymorphic variants *)
5454- let errors =
5555- [
5656- (`Parse_error, "Parse error");
5757- (`Invalid_request, "Invalid request");
5858- (`Method_not_found, "Method not found");
5959- (`Invalid_params, "Invalid params");
6060- (`Internal_error, "Internal error");
6161- (`Custom 1, "Application error");
6262- ]
6363- in
6464-6565- List.iter
6666- (fun (code, msg) ->
6767- let err = Proto.Control.Response.error_detail ~code ~message:msg () in
6868- Printf.printf "✓ Error [%d]: %s (typed)\n" err.code err.message)
6969- errors
7070-7171-let test_provoke_api_error ~sw ~env =
7272- print_endline "\nTesting API error from Claude...";
7373-7474- (* Configure client with an invalid model to provoke an API error *)
7575- let options =
7676- Claude.Options.default
7777- |> Claude.Options.with_model
7878- (Claude.Model.of_string "invalid-model-that-does-not-exist")
7979- in
8080-8181- Printf.printf "Creating client with invalid model...\n";
8282-8383- try
8484- let client =
8585- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
8686- ~clock:env#clock ()
8787- in
8888-8989- Printf.printf "Sending query to provoke API error...\n";
9090- Claude.Client.query client
9191- "Hello, this should fail with an invalid model error";
9292-9393- (* Process responses to see if we get an error *)
9494- let messages = Claude.Client.receive_all client in
9595-9696- let error_found = ref false in
9797- let text_error_found = ref false in
9898- List.iter
9999- (fun resp ->
100100- match resp with
101101- | Claude.Response.Error err ->
102102- error_found := true;
103103- Printf.printf "✓ Received structured error response: %s\n"
104104- (Claude.Response.Error.message err);
105105- Printf.printf " Is system error: %b\n"
106106- (Claude.Response.Error.is_system_error err);
107107- Printf.printf " Is assistant error: %b\n"
108108- (Claude.Response.Error.is_assistant_error err)
109109- | Claude.Response.Text text ->
110110- let content = Claude.Response.Text.content text in
111111- if
112112- String.length content > 0
113113- && (String.contains content '4' || String.contains content 'e')
114114- then begin
115115- text_error_found := true;
116116- Printf.printf "✓ Received error as text: %s\n" content
117117- end
118118- | Claude.Response.Complete result ->
119119- Printf.printf " Complete (duration: %dms)\n"
120120- (Claude.Response.Complete.duration_ms result)
121121- | _ -> ())
122122- messages;
123123-124124- if !error_found then
125125- Printf.printf "✓ Successfully caught structured error response\n"
126126- else if !text_error_found then
127127- Printf.printf "✓ Successfully caught error (returned as text)\n"
128128- else Printf.printf "✗ No error was returned (unexpected)\n"
129129- with
130130- | Claude.Transport.Connection_error msg ->
131131- Printf.printf "✓ Connection error as expected: %s\n" msg
132132- | exn ->
133133- Printf.printf "✗ Unexpected exception: %s\n" (Printexc.to_string exn);
134134- Printexc.print_backtrace stdout
135135-136136-let test_control_protocol_error () =
137137- print_endline "\nTesting control protocol error encoding/decoding...";
138138-139139- (* Test that we can create and encode a control protocol error using polymorphic variant codes *)
140140- let error_detail =
141141- Proto.Control.Response.error_detail ~code:`Invalid_params
142142- ~message:"Invalid params for permission request"
143143- ~data:
144144- (Jsont.Object
145145- ( [
146146- ( ("tool_name", Jsont.Meta.none),
147147- Jsont.String ("Write", Jsont.Meta.none) );
148148- ( ("reason", Jsont.Meta.none),
149149- Jsont.String
150150- ("Missing required file_path parameter", Jsont.Meta.none) );
151151- ],
152152- Jsont.Meta.none ))
153153- ()
154154- in
155155-156156- let error_response =
157157- Proto.Control.Response.error ~request_id:"test-req-456" ~error:error_detail
158158- ()
159159- in
160160-161161- match Jsont.Json.encode Proto.Control.Response.jsont error_response with
162162- | Ok json -> (
163163- let json_str =
164164- match Jsont_bytesrw.encode_string' Jsont.json json with
165165- | Ok s -> s
166166- | Error e -> Jsont.Error.to_string e
167167- in
168168- Printf.printf "✓ Encoded control error with data:\n %s\n" json_str;
169169-170170- (* Verify we can decode it back *)
171171- match Jsont.Json.decode Proto.Control.Response.jsont json with
172172- | Ok (Proto.Control.Response.Error decoded) -> (
173173- Printf.printf "✓ Decoded control error:\n";
174174- Printf.printf " Code: %d\n" decoded.error.code;
175175- Printf.printf " Message: %s\n" decoded.error.message;
176176- Printf.printf " Has data: %b\n" (Option.is_some decoded.error.data);
177177- match decoded.error.data with
178178- | Some data ->
179179- let data_str =
180180- match Jsont_bytesrw.encode_string' Jsont.json data with
181181- | Ok s -> s
182182- | Error e -> Jsont.Error.to_string e
183183- in
184184- Printf.printf " Data: %s\n" data_str
185185- | None -> ())
186186- | Ok _ -> print_endline "✗ Wrong response type"
187187- | Error e -> Printf.printf "✗ Decode failed: %s\n" e)
188188- | Error e -> Printf.printf "✗ Encode failed: %s\n" e
189189-190190-let test_hook_error ~sw ~env =
191191- print_endline "\nTesting hook callback errors trigger JSON-RPC error codes...";
192192-193193- (* Create a hook that will throw an exception *)
194194- let failing_hook input =
195195- Printf.printf "✓ Hook called for tool: %s\n"
196196- input.Claude.Hooks.PreToolUse.tool_name;
197197- failwith "Intentional hook failure to test error handling"
198198- in
199199-200200- (* Register the failing hook *)
201201- let hooks =
202202- Claude.Hooks.empty
203203- |> Claude.Hooks.on_pre_tool_use ~pattern:"Write" failing_hook
204204- in
205205-206206- let options =
207207- Claude.Options.default
208208- |> Claude.Options.with_hooks hooks
209209- |> Claude.Options.with_model (Claude.Model.of_string "haiku")
210210- in
211211-212212- Printf.printf "Creating client with failing hook...\n";
213213-214214- try
215215- let client =
216216- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
217217- ~clock:env#clock ()
218218- in
219219-220220- Printf.printf
221221- "Asking Claude to write a file (should trigger failing hook)...\n";
222222- Claude.Client.query client "Write 'test' to /tmp/test_hook_error.txt";
223223-224224- (* Process responses *)
225225- let messages = Claude.Client.receive_all client in
226226-227227- let hook_called = ref false in
228228- let error_found = ref false in
229229- List.iter
230230- (fun resp ->
231231- match resp with
232232- | Claude.Response.Tool_use tool ->
233233- let tool_name = Claude.Response.Tool_use.name tool in
234234- if tool_name = "Write" then begin
235235- hook_called := true;
236236- Printf.printf "✓ Write tool was called (hook intercepted it)\n"
237237- end
238238- | Claude.Response.Error err ->
239239- error_found := true;
240240- Printf.printf " Error response: %s\n"
241241- (Claude.Response.Error.message err)
242242- | Claude.Response.Complete _ -> Printf.printf " Query completed\n"
243243- | _ -> ())
244244- messages;
245245-246246- if !hook_called then
247247- Printf.printf "✓ Hook was triggered, exception caught by SDK\n"
248248- else
249249- Printf.printf
250250- " Note: Hook may not have been called if query didn't use Write tool\n";
251251-252252- Printf.printf "✓ Test completed (SDK sent -32603 Internal Error to CLI)\n"
253253- with exn ->
254254- Printf.printf "Exception during test: %s\n" (Printexc.to_string exn);
255255- Printexc.print_backtrace stdout
256256-257257-let run_all_tests env =
258258- print_endline "=== Structured Error Tests ===";
259259- test_create_error_detail ();
260260- test_error_code_conventions ();
261261- test_control_protocol_error ();
262262-263263- (* Test with actual Claude invocation *)
264264- Switch.run @@ fun sw ->
265265- test_provoke_api_error ~sw ~env;
266266-267267- (* Test hook errors that trigger JSON-RPC error codes *)
268268- Switch.run @@ fun sw ->
269269- test_hook_error ~sw ~env;
270270-271271- print_endline "\n=== All Structured Error Tests Completed ==="
272272-273273-let () =
274274- Eio_main.run @@ fun env ->
275275- try run_all_tests env with
276276- | Claude.Transport.CLI_not_found msg ->
277277- Printf.eprintf "Error: Claude CLI not found\n%s\n" msg;
278278- Printf.eprintf "Make sure 'claude' is installed and in your PATH\n";
279279- exit 1
280280- | exn ->
281281- Printf.eprintf "Fatal error: %s\n" (Printexc.to_string exn);
282282- Printexc.print_backtrace stderr;
283283- exit 1