OCaml Claude SDK using Eio and Jsont
at main 416 lines 15 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** Control protocol wire format for SDK communication. *) 7 8module Request = struct 9 (* Individual record types for each request variant - private to this module *) 10 type permission_r = { 11 tool_name : string; 12 input : Jsont.json; 13 permission_suggestions : Permissions.Update.t list option; 14 blocked_path : string option; 15 unknown : Unknown.t; 16 } 17 18 type initialize_r = { 19 hooks : (string * Jsont.json) list option; 20 unknown : Unknown.t; 21 } 22 23 type set_permission_mode_r = { 24 mode : Permissions.Mode.t; 25 unknown : Unknown.t; 26 } 27 28 type hook_callback_r = { 29 callback_id : string; 30 input : Jsont.json; 31 tool_use_id : string option; 32 unknown : Unknown.t; 33 } 34 35 type mcp_message_r = { 36 server_name : string; 37 message : Jsont.json; 38 unknown : Unknown.t; 39 } 40 41 type set_model_r = { model : string; unknown : Unknown.t } 42 43 type t = 44 | Interrupt 45 | Permission of permission_r 46 | Initialize of initialize_r 47 | Set_permission_mode of set_permission_mode_r 48 | Hook_callback of hook_callback_r 49 | Mcp_message of mcp_message_r 50 | Set_model of set_model_r 51 | Get_server_info 52 53 let interrupt () = Interrupt 54 55 let permission ~tool_name ~input ?permission_suggestions ?blocked_path () = 56 Permission 57 { 58 tool_name; 59 input; 60 permission_suggestions; 61 blocked_path; 62 unknown = Unknown.empty; 63 } 64 65 let initialize ?hooks () = Initialize { hooks; unknown = Unknown.empty } 66 67 let set_permission_mode ~mode () = 68 Set_permission_mode { mode; unknown = Unknown.empty } 69 70 let hook_callback ~callback_id ~input ?tool_use_id () = 71 Hook_callback { callback_id; input; tool_use_id; unknown = Unknown.empty } 72 73 let mcp_message ~server_name ~message () = 74 Mcp_message { server_name; message; unknown = Unknown.empty } 75 76 let set_model ~model () = Set_model { model; unknown = Unknown.empty } 77 let get_server_info () = Get_server_info 78 79 (* Individual record codecs *) 80 let interrupt_jsont : unit Jsont.t = 81 Jsont.Object.map ~kind:"Interrupt" (fun _unknown -> ()) 82 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun () -> Unknown.empty) 83 |> Jsont.Object.finish 84 85 let permission_jsont : permission_r Jsont.t = 86 let make tool_name input permission_suggestions blocked_path unknown : 87 permission_r = 88 { tool_name; input; permission_suggestions; blocked_path; unknown } 89 in 90 Jsont.Object.map ~kind:"Permission" make 91 |> Jsont.Object.mem "toolName" Jsont.string ~enc:(fun (r : permission_r) -> 92 r.tool_name) 93 |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : permission_r) -> 94 r.input) 95 |> Jsont.Object.opt_mem "permissionSuggestions" 96 (Jsont.list Permissions.Update.jsont) ~enc:(fun (r : permission_r) -> 97 r.permission_suggestions) 98 |> Jsont.Object.opt_mem "blockedPath" Jsont.string 99 ~enc:(fun (r : permission_r) -> r.blocked_path) 100 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : permission_r) -> 101 r.unknown) 102 |> Jsont.Object.finish 103 104 let initialize_jsont : initialize_r Jsont.t = 105 (* The hooks field is an object with string keys and json values *) 106 let hooks_map_jsont = Jsont.Object.as_string_map Jsont.json in 107 let module StringMap = Map.Make (String) in 108 let hooks_jsont = 109 Jsont.map 110 ~dec:(fun m -> StringMap.bindings m) 111 ~enc:(fun l -> StringMap.of_seq (List.to_seq l)) 112 hooks_map_jsont 113 in 114 let make hooks unknown = { hooks; unknown } in 115 Jsont.Object.map ~kind:"Initialize" make 116 |> Jsont.Object.opt_mem "hooks" hooks_jsont ~enc:(fun (r : initialize_r) -> 117 r.hooks) 118 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : initialize_r) -> 119 r.unknown) 120 |> Jsont.Object.finish 121 122 let set_permission_mode_jsont : set_permission_mode_r Jsont.t = 123 let make mode unknown = { mode; unknown } in 124 Jsont.Object.map ~kind:"SetPermissionMode" make 125 |> Jsont.Object.mem "mode" Permissions.Mode.jsont 126 ~enc:(fun (r : set_permission_mode_r) -> r.mode) 127 |> Jsont.Object.keep_unknown Unknown.mems 128 ~enc:(fun (r : set_permission_mode_r) -> r.unknown) 129 |> Jsont.Object.finish 130 131 let hook_callback_jsont : hook_callback_r Jsont.t = 132 let make callback_id input tool_use_id unknown = 133 { callback_id; input; tool_use_id; unknown } 134 in 135 Jsont.Object.map ~kind:"HookCallback" make 136 |> Jsont.Object.mem "callbackId" Jsont.string 137 ~enc:(fun (r : hook_callback_r) -> r.callback_id) 138 |> Jsont.Object.mem "input" Jsont.json ~enc:(fun (r : hook_callback_r) -> 139 r.input) 140 |> Jsont.Object.opt_mem "toolUseId" Jsont.string 141 ~enc:(fun (r : hook_callback_r) -> r.tool_use_id) 142 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : hook_callback_r) -> 143 r.unknown) 144 |> Jsont.Object.finish 145 146 let mcp_message_jsont : mcp_message_r Jsont.t = 147 let make server_name message unknown = { server_name; message; unknown } in 148 Jsont.Object.map ~kind:"McpMessage" make 149 |> Jsont.Object.mem "serverName" Jsont.string 150 ~enc:(fun (r : mcp_message_r) -> r.server_name) 151 |> Jsont.Object.mem "message" Jsont.json ~enc:(fun (r : mcp_message_r) -> 152 r.message) 153 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : mcp_message_r) -> 154 r.unknown) 155 |> Jsont.Object.finish 156 157 let set_model_jsont : set_model_r Jsont.t = 158 let make model unknown = { model; unknown } in 159 Jsont.Object.map ~kind:"SetModel" make 160 |> Jsont.Object.mem "model" Jsont.string ~enc:(fun (r : set_model_r) -> 161 r.model) 162 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : set_model_r) -> 163 r.unknown) 164 |> Jsont.Object.finish 165 166 let get_server_info_jsont : unit Jsont.t = 167 Jsont.Object.map ~kind:"GetServerInfo" (fun _unknown -> ()) 168 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun () -> Unknown.empty) 169 |> Jsont.Object.finish 170 171 (* Main variant codec using subtype discriminator *) 172 let jsont : t Jsont.t = 173 let case_interrupt = 174 Jsont.Object.Case.map "interrupt" interrupt_jsont ~dec:(fun () -> 175 Interrupt) 176 in 177 let case_permission = 178 Jsont.Object.Case.map "canUseTool" permission_jsont ~dec:(fun v -> 179 Permission v) 180 in 181 let case_initialize = 182 Jsont.Object.Case.map "initialize" initialize_jsont ~dec:(fun v -> 183 Initialize v) 184 in 185 let case_set_permission_mode = 186 Jsont.Object.Case.map "setPermissionMode" set_permission_mode_jsont 187 ~dec:(fun v -> Set_permission_mode v) 188 in 189 let case_hook_callback = 190 Jsont.Object.Case.map "hookCallback" hook_callback_jsont ~dec:(fun v -> 191 Hook_callback v) 192 in 193 let case_mcp_message = 194 Jsont.Object.Case.map "mcpMessage" mcp_message_jsont ~dec:(fun v -> 195 Mcp_message v) 196 in 197 let case_set_model = 198 Jsont.Object.Case.map "setModel" set_model_jsont ~dec:(fun v -> 199 Set_model v) 200 in 201 let case_get_server_info = 202 Jsont.Object.Case.map "getServerInfo" get_server_info_jsont 203 ~dec:(fun () -> Get_server_info) 204 in 205 206 let enc_case = function 207 | Interrupt -> Jsont.Object.Case.value case_interrupt () 208 | Permission v -> Jsont.Object.Case.value case_permission v 209 | Initialize v -> Jsont.Object.Case.value case_initialize v 210 | Set_permission_mode v -> 211 Jsont.Object.Case.value case_set_permission_mode v 212 | Hook_callback v -> Jsont.Object.Case.value case_hook_callback v 213 | Mcp_message v -> Jsont.Object.Case.value case_mcp_message v 214 | Set_model v -> Jsont.Object.Case.value case_set_model v 215 | Get_server_info -> Jsont.Object.Case.value case_get_server_info () 216 in 217 218 let cases = 219 Jsont.Object.Case. 220 [ 221 make case_interrupt; 222 make case_permission; 223 make case_initialize; 224 make case_set_permission_mode; 225 make case_hook_callback; 226 make case_mcp_message; 227 make case_set_model; 228 make case_get_server_info; 229 ] 230 in 231 232 Jsont.Object.map ~kind:"Request" Fun.id 233 |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases 234 ~tag_to_string:Fun.id ~tag_compare:String.compare 235 |> Jsont.Object.finish 236end 237 238module Response = struct 239 (* Standard JSON-RPC 2.0 error codes using polymorphic variants *) 240 module Error_code = struct 241 type t = 242 [ `Parse_error 243 | `Invalid_request 244 | `Method_not_found 245 | `Invalid_params 246 | `Internal_error 247 | `Custom of int ] 248 249 let to_int : [< t ] -> int = function 250 | `Parse_error -> -32700 251 | `Invalid_request -> -32600 252 | `Method_not_found -> -32601 253 | `Invalid_params -> -32602 254 | `Internal_error -> -32603 255 | `Custom n -> n 256 257 let of_int = function 258 | -32700 -> `Parse_error 259 | -32600 -> `Invalid_request 260 | -32601 -> `Method_not_found 261 | -32602 -> `Invalid_params 262 | -32603 -> `Internal_error 263 | n -> `Custom n 264 end 265 266 (* Structured error similar to JSON-RPC *) 267 type error_detail = { code : int; message : string; data : Jsont.json option } 268 269 let error_detail ~code ~message ?data () = 270 { code = Error_code.to_int code; message; data } 271 272 let error_detail_jsont : error_detail Jsont.t = 273 let make code message data = { code; message; data } in 274 Jsont.Object.map ~kind:"ErrorDetail" make 275 |> Jsont.Object.mem "code" Jsont.int ~enc:(fun e -> e.code) 276 |> Jsont.Object.mem "message" Jsont.string ~enc:(fun e -> e.message) 277 |> Jsont.Object.opt_mem "data" Jsont.json ~enc:(fun e -> e.data) 278 |> Jsont.Object.finish 279 280 (* Individual record types for each response variant *) 281 type success_r = { 282 request_id : string; 283 response : Jsont.json option; 284 unknown : Unknown.t; 285 } 286 287 type error_r = { 288 request_id : string; 289 error : error_detail; 290 unknown : Unknown.t; 291 } 292 293 type t = Success of success_r | Error of error_r 294 295 let success ~request_id ?response () = 296 Success { request_id; response; unknown = Unknown.empty } 297 298 let error ~request_id ~error () = 299 Error { request_id; error; unknown = Unknown.empty } 300 301 (* Individual record codecs *) 302 let success_jsont : success_r Jsont.t = 303 let make request_id response unknown = { request_id; response; unknown } in 304 Jsont.Object.map ~kind:"Success" make 305 |> Jsont.Object.mem "requestId" Jsont.string ~enc:(fun (r : success_r) -> 306 r.request_id) 307 |> Jsont.Object.opt_mem "response" Jsont.json ~enc:(fun (r : success_r) -> 308 r.response) 309 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : success_r) -> 310 r.unknown) 311 |> Jsont.Object.finish 312 313 let error_jsont : error_r Jsont.t = 314 let make request_id error unknown = { request_id; error; unknown } in 315 Jsont.Object.map ~kind:"Error" make 316 |> Jsont.Object.mem "requestId" Jsont.string ~enc:(fun (r : error_r) -> 317 r.request_id) 318 |> Jsont.Object.mem "error" error_detail_jsont ~enc:(fun (r : error_r) -> 319 r.error) 320 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : error_r) -> 321 r.unknown) 322 |> Jsont.Object.finish 323 324 (* Main variant codec using subtype discriminator *) 325 let jsont : t Jsont.t = 326 let case_success = 327 Jsont.Object.Case.map "success" success_jsont ~dec:(fun v -> Success v) 328 in 329 let case_error = 330 Jsont.Object.Case.map "error" error_jsont ~dec:(fun v -> Error v) 331 in 332 333 let enc_case = function 334 | Success v -> Jsont.Object.Case.value case_success v 335 | Error v -> Jsont.Object.Case.value case_error v 336 in 337 338 let cases = Jsont.Object.Case.[ make case_success; make case_error ] in 339 340 Jsont.Object.map ~kind:"Response" Fun.id 341 |> Jsont.Object.case_mem "subtype" Jsont.string ~enc:Fun.id ~enc_case cases 342 ~tag_to_string:Fun.id ~tag_compare:String.compare 343 |> Jsont.Object.finish 344end 345 346type request_envelope = { 347 request_id : string; 348 request : Request.t; 349 unknown : Unknown.t; 350} 351 352type response_envelope = { response : Response.t; unknown : Unknown.t } 353 354let create_request ~request_id ~request () = 355 { request_id; request; unknown = Unknown.empty } 356 357let create_response ~response () = { response; unknown = Unknown.empty } 358 359(* Envelope codecs *) 360let request_envelope_jsont : request_envelope Jsont.t = 361 let make request_id request unknown = { request_id; request; unknown } in 362 Jsont.Object.map ~kind:"RequestEnvelope" make 363 |> Jsont.Object.mem "requestId" Jsont.string 364 ~enc:(fun (r : request_envelope) -> r.request_id) 365 |> Jsont.Object.mem "request" Request.jsont 366 ~enc:(fun (r : request_envelope) -> r.request) 367 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : request_envelope) -> 368 r.unknown) 369 |> Jsont.Object.finish 370 371let response_envelope_jsont : response_envelope Jsont.t = 372 let make response unknown = { response; unknown } in 373 Jsont.Object.map ~kind:"ResponseEnvelope" make 374 |> Jsont.Object.mem "response" Response.jsont 375 ~enc:(fun (r : response_envelope) -> r.response) 376 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun (r : response_envelope) -> 377 r.unknown) 378 |> Jsont.Object.finish 379 380(** Server information *) 381module Server_info = struct 382 type t = { 383 version : string; 384 capabilities : string list; 385 commands : string list; 386 output_styles : string list; 387 unknown : Unknown.t; 388 } 389 390 let create ~version ~capabilities ~commands ~output_styles () = 391 { version; capabilities; commands; output_styles; unknown = Unknown.empty } 392 393 let version t = t.version 394 let capabilities t = t.capabilities 395 let commands t = t.commands 396 let output_styles t = t.output_styles 397 let unknown t = t.unknown 398 399 let jsont : t Jsont.t = 400 let make version capabilities commands output_styles unknown = 401 { version; capabilities; commands; output_styles; unknown } 402 in 403 Jsont.Object.map ~kind:"ServerInfo" make 404 |> Jsont.Object.mem "version" Jsont.string ~enc:(fun r -> r.version) 405 |> Jsont.Object.mem "capabilities" (Jsont.list Jsont.string) 406 ~enc:(fun r -> r.capabilities) 407 ~dec_absent:[] 408 |> Jsont.Object.mem "commands" (Jsont.list Jsont.string) 409 ~enc:(fun r -> r.commands) 410 ~dec_absent:[] 411 |> Jsont.Object.mem "outputStyles" (Jsont.list Jsont.string) 412 ~enc:(fun r -> r.output_styles) 413 ~dec_absent:[] 414 |> Jsont.Object.keep_unknown Unknown.mems ~enc:(fun r -> r.unknown) 415 |> Jsont.Object.finish 416end