···
3
+
let src = Logs.Src.create "claude.sdk_control" ~doc:"Claude SDK control protocol"
4
+
module Log = (val Logs.src_log src : Logs.LOG)
6
+
module JU = Json_utils
8
+
module Request = struct
10
+
subtype : [`Interrupt];
14
+
subtype : [`Can_use_tool];
17
+
permission_suggestions : Permissions.Update.t list option;
18
+
blocked_path : string option;
22
+
subtype : [`Initialize];
23
+
hooks : (string * value) list option;
26
+
type set_permission_mode = {
27
+
subtype : [`Set_permission_mode];
28
+
mode : Permissions.Mode.t;
31
+
type hook_callback = {
32
+
subtype : [`Hook_callback];
33
+
callback_id : string;
35
+
tool_use_id : string option;
38
+
type mcp_message = {
39
+
subtype : [`Mcp_message];
40
+
server_name : string;
45
+
| Interrupt of interrupt
46
+
| Permission of permission
47
+
| Initialize of initialize
48
+
| Set_permission_mode of set_permission_mode
49
+
| Hook_callback of hook_callback
50
+
| Mcp_message of mcp_message
52
+
let interrupt () = Interrupt { subtype = `Interrupt }
54
+
let permission ~tool_name ~input ?permission_suggestions ?blocked_path () =
56
+
subtype = `Can_use_tool;
59
+
permission_suggestions;
63
+
let initialize ?hooks () =
64
+
Initialize { subtype = `Initialize; hooks }
66
+
let set_permission_mode ~mode =
67
+
Set_permission_mode { subtype = `Set_permission_mode; mode }
69
+
let hook_callback ~callback_id ~input ?tool_use_id () =
71
+
subtype = `Hook_callback;
77
+
let mcp_message ~server_name ~message =
79
+
subtype = `Mcp_message;
84
+
let to_json = function
86
+
`O [("subtype", `String "interrupt")]
89
+
("subtype", `String "can_use_tool");
90
+
("tool_name", `String p.tool_name);
93
+
let fields = match p.permission_suggestions with
94
+
| Some suggestions ->
95
+
("permission_suggestions",
96
+
`A (List.map Permissions.Update.to_json suggestions)) :: fields
99
+
let fields = match p.blocked_path with
100
+
| Some path -> ("blocked_path", `String path) :: fields
105
+
let fields = [("subtype", `String "initialize")] in
106
+
let fields = match i.hooks with
108
+
("hooks", `O hooks) :: fields
112
+
| Set_permission_mode s ->
114
+
("subtype", `String "set_permission_mode");
115
+
("mode", Permissions.Mode.to_json s.mode);
117
+
| Hook_callback h ->
119
+
("subtype", `String "hook_callback");
120
+
("callback_id", `String h.callback_id);
121
+
("input", h.input);
123
+
let fields = match h.tool_use_id with
124
+
| Some id -> ("tool_use_id", `String id) :: fields
130
+
("subtype", `String "mcp_message");
131
+
("server_name", `String m.server_name);
132
+
("message", m.message);
135
+
let of_json = function
137
+
let subtype = JU.assoc_string "subtype" fields in
138
+
(match subtype with
140
+
Interrupt { subtype = `Interrupt }
141
+
| "can_use_tool" ->
142
+
let tool_name = JU.assoc_string "tool_name" fields in
143
+
let input = List.assoc "input" fields in
144
+
let permission_suggestions =
145
+
match List.assoc_opt "permission_suggestions" fields with
147
+
Some (List.map Permissions.Update.of_json lst)
150
+
let blocked_path = JU.assoc_string_opt "blocked_path" fields in
152
+
subtype = `Can_use_tool;
155
+
permission_suggestions;
160
+
match List.assoc_opt "hooks" fields with
161
+
| Some (`O hooks) -> Some hooks
164
+
Initialize { subtype = `Initialize; hooks }
165
+
| "set_permission_mode" ->
166
+
let mode = List.assoc "mode" fields |> Permissions.Mode.of_json in
167
+
Set_permission_mode { subtype = `Set_permission_mode; mode }
168
+
| "hook_callback" ->
169
+
let callback_id = JU.assoc_string "callback_id" fields in
170
+
let input = List.assoc "input" fields in
171
+
let tool_use_id = JU.assoc_string_opt "tool_use_id" fields in
173
+
subtype = `Hook_callback;
179
+
let server_name = JU.assoc_string "server_name" fields in
180
+
let message = List.assoc "message" fields in
182
+
subtype = `Mcp_message;
186
+
| _ -> raise (Invalid_argument ("Unknown request subtype: " ^ subtype)))
187
+
| _ -> raise (Invalid_argument "Request.of_json: expected object")
189
+
let pp fmt = function
191
+
Fmt.pf fmt "@[<2>Interrupt@]"
193
+
Fmt.pf fmt "@[<2>Permission@ { tool = %S;@ blocked_path = %a }@]"
194
+
p.tool_name Fmt.(option string) p.blocked_path
196
+
Fmt.pf fmt "@[<2>Initialize@ { hooks = %s }@]"
197
+
(if Option.is_some i.hooks then "present" else "none")
198
+
| Set_permission_mode s ->
199
+
Fmt.pf fmt "@[<2>SetPermissionMode@ { mode = %a }@]"
200
+
Permissions.Mode.pp s.mode
201
+
| Hook_callback h ->
202
+
Fmt.pf fmt "@[<2>HookCallback@ { id = %S;@ tool_use_id = %a }@]"
203
+
h.callback_id Fmt.(option string) h.tool_use_id
205
+
Fmt.pf fmt "@[<2>McpMessage@ { server = %S }@]"
209
+
module Response = struct
211
+
subtype : [`Success];
212
+
request_id : string;
213
+
response : value option;
217
+
subtype : [`Error];
218
+
request_id : string;
223
+
| Success of success
226
+
let success ~request_id ?response () =
228
+
subtype = `Success;
233
+
let error ~request_id ~error =
240
+
let to_json = function
243
+
("subtype", `String "success");
244
+
("request_id", `String s.request_id);
246
+
let fields = match s.response with
247
+
| Some resp -> ("response", resp) :: fields
253
+
("subtype", `String "error");
254
+
("request_id", `String e.request_id);
255
+
("error", `String e.error);
258
+
let of_json = function
260
+
let subtype = JU.assoc_string "subtype" fields in
261
+
let request_id = JU.assoc_string "request_id" fields in
262
+
(match subtype with
264
+
let response = List.assoc_opt "response" fields in
266
+
subtype = `Success;
271
+
let error = JU.assoc_string "error" fields in
277
+
| _ -> raise (Invalid_argument ("Unknown response subtype: " ^ subtype)))
278
+
| _ -> raise (Invalid_argument "Response.of_json: expected object")
280
+
let pp fmt = function
282
+
Fmt.pf fmt "@[<2>Success@ { request_id = %S;@ response = %s }@]"
283
+
s.request_id (if Option.is_some s.response then "present" else "none")
285
+
Fmt.pf fmt "@[<2>Error@ { request_id = %S;@ error = %S }@]"
286
+
e.request_id e.error
289
+
type control_request = {
290
+
type_ : [`Control_request];
291
+
request_id : string;
292
+
request : Request.t;
295
+
type control_response = {
296
+
type_ : [`Control_response];
297
+
response : Response.t;
301
+
| Request of control_request
302
+
| Response of control_response
304
+
let create_request ~request_id ~request =
306
+
type_ = `Control_request;
311
+
let create_response ~response =
313
+
type_ = `Control_response;
317
+
let to_json = function
320
+
("type", `String "control_request");
321
+
("request_id", `String r.request_id);
322
+
("request", Request.to_json r.request);
326
+
("type", `String "control_response");
327
+
("response", Response.to_json r.response);
330
+
let of_json = function
332
+
let type_ = JU.assoc_string "type" fields in
334
+
| "control_request" ->
335
+
let request_id = JU.assoc_string "request_id" fields in
336
+
let request = List.assoc "request" fields |> Request.of_json in
338
+
type_ = `Control_request;
342
+
| "control_response" ->
343
+
let response = List.assoc "response" fields |> Response.of_json in
345
+
type_ = `Control_response;
348
+
| _ -> raise (Invalid_argument ("Unknown control type: " ^ type_)))
349
+
| _ -> raise (Invalid_argument "of_json: expected object")
351
+
let pp fmt = function
353
+
Fmt.pf fmt "@[<2>ControlRequest@ { id = %S;@ request = %a }@]"
354
+
r.request_id Request.pp r.request
356
+
Fmt.pf fmt "@[<2>ControlResponse@ { %a }@]"
357
+
Response.pp r.response
359
+
let log_request req =
360
+
Log.debug (fun m -> m "SDK control request: %a" Request.pp req)
362
+
let log_response resp =
363
+
Log.debug (fun m -> m "SDK control response: %a" Response.pp resp)