My agentic slop goes here. Not intended for anyone else!
at main 16 kB view raw
1let src = Logs.Src.create "claude.options" ~doc:"Claude configuration options" 2module Log = (val Logs.src_log src : Logs.LOG) 3 4type setting_source = User | Project | Local 5 6type mcp_stdio_config = { 7 command : string; 8 args : string list; 9 env : (string * string) list option; 10} 11 12type mcp_sse_config = { 13 url : string; 14 headers : (string * string) list option; 15} 16 17type mcp_http_config = { 18 url : string; 19 headers : (string * string) list option; 20} 21 22type mcp_server_config = 23 | Stdio of mcp_stdio_config 24 | SSE of mcp_sse_config 25 | HTTP of mcp_http_config 26 27module Unknown = struct 28 type t = Jsont.json 29 let empty = Jsont.Object ([], Jsont.Meta.none) 30 let _is_empty = function Jsont.Object ([], _) -> true | _ -> false 31 let _jsont = Jsont.json 32end 33 34type t = { 35 allowed_tools : string list; 36 disallowed_tools : string list; 37 max_thinking_tokens : int; 38 system_prompt : string option; 39 append_system_prompt : string option; 40 permission_mode : Permissions.Mode.t option; 41 permission_callback : Permissions.callback option; 42 model : Model.t option; 43 cwd : Eio.Fs.dir_ty Eio.Path.t option; 44 env : (string * string) list; 45 continue_conversation : bool; 46 resume : string option; 47 max_turns : int option; 48 permission_prompt_tool_name : string option; 49 settings : string option; 50 add_dirs : string list; 51 extra_args : (string * string option) list; 52 debug_stderr : Eio.Flow.sink_ty Eio.Flow.sink option; 53 hooks : Hooks.config option; 54 max_budget_usd : float option; 55 fallback_model : Model.t option; 56 setting_sources : setting_source list option; 57 max_buffer_size : int option; 58 user : string option; 59 output_format : Structured_output.t option; 60 mcp_servers : (string * mcp_server_config) list; 61 unknown : Unknown.t; 62} 63 64let default = { 65 allowed_tools = []; 66 disallowed_tools = []; 67 max_thinking_tokens = 8000; 68 system_prompt = None; 69 append_system_prompt = None; 70 permission_mode = None; 71 permission_callback = Some Permissions.default_allow_callback; 72 model = None; 73 cwd = None; 74 env = []; 75 continue_conversation = false; 76 resume = None; 77 max_turns = None; 78 permission_prompt_tool_name = None; 79 settings = None; 80 add_dirs = []; 81 extra_args = []; 82 debug_stderr = None; 83 hooks = None; 84 max_budget_usd = None; 85 fallback_model = None; 86 setting_sources = None; 87 max_buffer_size = None; 88 user = None; 89 output_format = None; 90 mcp_servers = []; 91 unknown = Unknown.empty; 92} 93 94let create 95 ?(allowed_tools = []) 96 ?(disallowed_tools = []) 97 ?(max_thinking_tokens = 8000) 98 ?system_prompt 99 ?append_system_prompt 100 ?permission_mode 101 ?permission_callback 102 ?model 103 ?cwd 104 ?(env = []) 105 ?(continue_conversation = false) 106 ?resume 107 ?max_turns 108 ?permission_prompt_tool_name 109 ?settings 110 ?(add_dirs = []) 111 ?(extra_args = []) 112 ?debug_stderr 113 ?hooks 114 ?max_budget_usd 115 ?fallback_model 116 ?setting_sources 117 ?max_buffer_size 118 ?user 119 ?output_format 120 ?(mcp_servers = []) 121 ?(unknown = Unknown.empty) 122 () = 123 { allowed_tools; disallowed_tools; max_thinking_tokens; 124 system_prompt; append_system_prompt; permission_mode; 125 permission_callback; model; cwd; env; 126 continue_conversation; resume; max_turns; 127 permission_prompt_tool_name; settings; add_dirs; 128 extra_args; debug_stderr; hooks; 129 max_budget_usd; fallback_model; setting_sources; 130 max_buffer_size; user; output_format; mcp_servers; unknown } 131 132let allowed_tools t = t.allowed_tools 133let disallowed_tools t = t.disallowed_tools 134let max_thinking_tokens t = t.max_thinking_tokens 135let system_prompt t = t.system_prompt 136let append_system_prompt t = t.append_system_prompt 137let permission_mode t = t.permission_mode 138let permission_callback t = t.permission_callback 139let model t = t.model 140let cwd t = t.cwd 141let env t = t.env 142let continue_conversation t = t.continue_conversation 143let resume t = t.resume 144let max_turns t = t.max_turns 145let permission_prompt_tool_name t = t.permission_prompt_tool_name 146let settings t = t.settings 147let add_dirs t = t.add_dirs 148let extra_args t = t.extra_args 149let debug_stderr t = t.debug_stderr 150let hooks t = t.hooks 151let max_budget_usd t = t.max_budget_usd 152let fallback_model t = t.fallback_model 153let setting_sources t = t.setting_sources 154let max_buffer_size t = t.max_buffer_size 155let user t = t.user 156let output_format t = t.output_format 157let mcp_servers t = t.mcp_servers 158let unknown t = t.unknown 159 160let with_allowed_tools tools t = { t with allowed_tools = tools } 161let with_disallowed_tools tools t = { t with disallowed_tools = tools } 162let with_max_thinking_tokens tokens t = { t with max_thinking_tokens = tokens } 163let with_system_prompt prompt t = { t with system_prompt = Some prompt } 164let with_append_system_prompt prompt t = { t with append_system_prompt = Some prompt } 165let with_permission_mode mode t = { t with permission_mode = Some mode } 166let with_permission_callback callback t = { t with permission_callback = Some callback } 167let with_model model t = { t with model = Some model } 168let with_model_string model t = { t with model = Some (Model.of_string model) } 169let with_cwd cwd t = { t with cwd = Some cwd } 170let with_env env t = { t with env } 171let with_continue_conversation continue t = { t with continue_conversation = continue } 172let with_resume session_id t = { t with resume = Some session_id } 173let with_max_turns turns t = { t with max_turns = Some turns } 174let with_permission_prompt_tool_name tool t = { t with permission_prompt_tool_name = Some tool } 175let with_settings path t = { t with settings = Some path } 176let with_add_dirs dirs t = { t with add_dirs = dirs } 177let with_extra_args args t = { t with extra_args = args } 178let with_debug_stderr sink t = { t with debug_stderr = Some sink } 179let with_hooks hooks t = { t with hooks = Some hooks } 180let with_max_budget_usd budget t = { t with max_budget_usd = Some budget } 181let with_fallback_model model t = { t with fallback_model = Some model } 182let with_fallback_model_string model t = { t with fallback_model = Some (Model.of_string model) } 183let with_setting_sources sources t = { t with setting_sources = Some sources } 184let with_no_settings t = { t with setting_sources = Some [] } 185let with_max_buffer_size size t = { t with max_buffer_size = Some size } 186let with_user user t = { t with user = Some user } 187let with_output_format format t = { t with output_format = Some format } 188 189let with_mcp_server ~name ~config t = 190 let servers = List.filter (fun (n, _) -> n <> name) t.mcp_servers in 191 { t with mcp_servers = (name, config) :: servers } 192 193let with_mcp_servers servers t = { t with mcp_servers = servers } 194 195let with_mcp_stdio ~name ~command ?(args = []) ?env () t = 196 let config = Stdio { command; args; env } in 197 with_mcp_server ~name ~config t 198 199(* Helper codec for Model.t *) 200let model_jsont : Model.t Jsont.t = 201 Jsont.map ~kind:"Model" 202 ~dec:Model.of_string 203 ~enc:Model.to_string 204 Jsont.string 205 206(* Helper codec for env - list of string pairs encoded as object. 207 Env is a dynamic object where all values should be strings. 208 Uses pattern matching to extract object members, then jsont for string decoding. *) 209let env_jsont : (string * string) list Jsont.t = 210 Jsont.map ~kind:"Env" 211 ~dec:(fun json -> 212 match json with 213 | Jsont.Object (members, _) -> 214 List.filter_map (fun ((name, _), value) -> 215 match Jsont.Json.decode Jsont.string value with 216 | Ok s -> Some (name, s) 217 | Error _ -> None 218 ) members 219 | _ -> []) 220 ~enc:(fun pairs -> 221 Jsont.Json.object' (List.map (fun (k, v) -> 222 Jsont.Json.mem (Jsont.Json.name k) (Jsont.Json.string v) 223 ) pairs)) 224 Jsont.json 225 226(* Helper codec for headers - list of string pairs encoded as object *) 227let headers_jsont : (string * string) list Jsont.t = 228 Jsont.map ~kind:"Headers" 229 ~dec:(fun obj -> 230 match obj with 231 | Jsont.Object (members, _) -> 232 List.map (fun ((name, _), value) -> 233 match value with 234 | Jsont.String (s, _) -> (name, s) 235 | _ -> (name, "") 236 ) members 237 | _ -> []) 238 ~enc:(fun pairs -> 239 let mems = List.map (fun (k, v) -> 240 Jsont.Json.mem (Jsont.Json.name k) (Jsont.Json.string v) 241 ) pairs in 242 Jsont.Json.object' mems) 243 Jsont.json 244 245(* MCP server config codecs *) 246let mcp_stdio_config_jsont : mcp_stdio_config Jsont.t = 247 let make command args env : mcp_stdio_config = { command; args; env } in 248 Jsont.Object.map ~kind:"McpStdioConfig" make 249 |> Jsont.Object.mem "command" Jsont.string ~enc:(fun (c : mcp_stdio_config) -> c.command) ~dec_absent:"" 250 |> Jsont.Object.mem "args" (Jsont.list Jsont.string) ~enc:(fun (c : mcp_stdio_config) -> c.args) ~dec_absent:[] 251 |> Jsont.Object.opt_mem "env" env_jsont ~enc:(fun (c : mcp_stdio_config) -> c.env) 252 |> Jsont.Object.finish 253 254let mcp_sse_config_jsont : mcp_sse_config Jsont.t = 255 let make url headers : mcp_sse_config = { url; headers } in 256 Jsont.Object.map ~kind:"McpSseConfig" make 257 |> Jsont.Object.mem "url" Jsont.string ~enc:(fun (c : mcp_sse_config) -> c.url) ~dec_absent:"" 258 |> Jsont.Object.opt_mem "headers" headers_jsont ~enc:(fun (c : mcp_sse_config) -> c.headers) 259 |> Jsont.Object.finish 260 261let mcp_http_config_jsont : mcp_http_config Jsont.t = 262 let make url headers : mcp_http_config = { url; headers } in 263 Jsont.Object.map ~kind:"McpHttpConfig" make 264 |> Jsont.Object.mem "url" Jsont.string ~enc:(fun (c : mcp_http_config) -> c.url) ~dec_absent:"" 265 |> Jsont.Object.opt_mem "headers" headers_jsont ~enc:(fun (c : mcp_http_config) -> c.headers) 266 |> Jsont.Object.finish 267 268let mcp_server_config_jsont : mcp_server_config Jsont.t = 269 Jsont.map ~kind:"McpServerConfig" 270 ~dec:(fun obj -> 271 match obj with 272 | Jsont.Object (members, _) -> 273 (* Look for type field to determine variant *) 274 let type_field = List.find_map (fun ((name, _), value) -> 275 if name = "type" then 276 match value with 277 | Jsont.String (s, _) -> Some s 278 | _ -> None 279 else None 280 ) members in 281 (match type_field with 282 | Some "stdio" -> 283 let config = Jsont.Json.decode mcp_stdio_config_jsont obj in 284 (match config with 285 | Ok cfg -> Stdio cfg 286 | Error _ -> Stdio { command = ""; args = []; env = None }) 287 | Some "sse" -> 288 let config = Jsont.Json.decode mcp_sse_config_jsont obj in 289 (match config with 290 | Ok cfg -> SSE cfg 291 | Error _ -> SSE { url = ""; headers = None }) 292 | Some "http" -> 293 let config = Jsont.Json.decode mcp_http_config_jsont obj in 294 (match config with 295 | Ok cfg -> HTTP cfg 296 | Error _ -> HTTP { url = ""; headers = None }) 297 | _ -> Stdio { command = ""; args = []; env = None }) 298 | _ -> Stdio { command = ""; args = []; env = None }) 299 ~enc:(fun config -> 300 match config with 301 | Stdio cfg -> 302 let obj = Jsont.Json.encode mcp_stdio_config_jsont cfg in 303 (match obj with 304 | Ok (Jsont.Object (members, meta)) -> 305 let type_mem = Jsont.Json.mem (Jsont.Json.name "type") (Jsont.Json.string "stdio") in 306 Jsont.Object (type_mem :: members, meta) 307 | Ok json -> json 308 | Error _ -> Jsont.Object ([], Jsont.Meta.none)) 309 | SSE cfg -> 310 let obj = Jsont.Json.encode mcp_sse_config_jsont cfg in 311 (match obj with 312 | Ok (Jsont.Object (members, meta)) -> 313 let type_mem = Jsont.Json.mem (Jsont.Json.name "type") (Jsont.Json.string "sse") in 314 Jsont.Object (type_mem :: members, meta) 315 | Ok json -> json 316 | Error _ -> Jsont.Object ([], Jsont.Meta.none)) 317 | HTTP cfg -> 318 let obj = Jsont.Json.encode mcp_http_config_jsont cfg in 319 (match obj with 320 | Ok (Jsont.Object (members, meta)) -> 321 let type_mem = Jsont.Json.mem (Jsont.Json.name "type") (Jsont.Json.string "http") in 322 Jsont.Object (type_mem :: members, meta) 323 | Ok json -> json 324 | Error _ -> Jsont.Object ([], Jsont.Meta.none))) 325 Jsont.json 326 327(* Codec for MCP servers map - encoded as object with server names as keys *) 328let mcp_servers_jsont : (string * mcp_server_config) list Jsont.t = 329 Jsont.map ~kind:"McpServers" 330 ~dec:(fun obj -> 331 match obj with 332 | Jsont.Object (members, _) -> 333 List.filter_map (fun ((name, _), value) -> 334 match Jsont.Json.decode mcp_server_config_jsont value with 335 | Ok cfg -> Some (name, cfg) 336 | Error _ -> None 337 ) members 338 | _ -> []) 339 ~enc:(fun servers -> 340 let mems = List.map (fun (name, cfg) -> 341 match Jsont.Json.encode mcp_server_config_jsont cfg with 342 | Ok json -> Jsont.Json.mem (Jsont.Json.name name) json 343 | Error _ -> Jsont.Json.mem (Jsont.Json.name name) (Jsont.Object ([], Jsont.Meta.none)) 344 ) servers in 345 Jsont.Json.object' mems) 346 Jsont.json 347 348let jsont : t Jsont.t = 349 let make allowed_tools disallowed_tools max_thinking_tokens 350 system_prompt append_system_prompt permission_mode 351 model env mcp_servers unknown = 352 { allowed_tools; disallowed_tools; max_thinking_tokens; 353 system_prompt; append_system_prompt; permission_mode; 354 permission_callback = Some Permissions.default_allow_callback; 355 model; cwd = None; env; 356 continue_conversation = false; 357 resume = None; 358 max_turns = None; 359 permission_prompt_tool_name = None; 360 settings = None; 361 add_dirs = []; 362 extra_args = []; 363 debug_stderr = None; 364 hooks = None; 365 max_budget_usd = None; 366 fallback_model = None; 367 setting_sources = None; 368 max_buffer_size = None; 369 user = None; 370 output_format = None; 371 mcp_servers; 372 unknown } 373 in 374 Jsont.Object.map ~kind:"Options" make 375 |> Jsont.Object.mem "allowed_tools" (Jsont.list Jsont.string) ~enc:allowed_tools ~dec_absent:[] 376 |> Jsont.Object.mem "disallowed_tools" (Jsont.list Jsont.string) ~enc:disallowed_tools ~dec_absent:[] 377 |> Jsont.Object.mem "max_thinking_tokens" Jsont.int ~enc:max_thinking_tokens ~dec_absent:8000 378 |> Jsont.Object.opt_mem "system_prompt" Jsont.string ~enc:system_prompt 379 |> Jsont.Object.opt_mem "append_system_prompt" Jsont.string ~enc:append_system_prompt 380 |> Jsont.Object.opt_mem "permission_mode" Permissions.Mode.jsont ~enc:permission_mode 381 |> Jsont.Object.opt_mem "model" model_jsont ~enc:model 382 |> Jsont.Object.mem "env" env_jsont ~enc:env ~dec_absent:[] 383 |> Jsont.Object.mem "mcp_servers" mcp_servers_jsont ~enc:mcp_servers ~dec_absent:[] 384 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown 385 |> Jsont.Object.finish 386 387let to_json t = 388 match Jsont.Json.encode jsont t with 389 | Ok json -> json 390 | Error msg -> failwith ("Options.to_json: " ^ msg) 391 392let of_json json = 393 match Jsont.Json.decode jsont json with 394 | Ok t -> t 395 | Error msg -> raise (Invalid_argument ("Options.of_json: " ^ msg)) 396 397let pp fmt t = 398 Fmt.pf fmt "@[<v>Options {@ \ 399 allowed_tools = %a;@ \ 400 disallowed_tools = %a;@ \ 401 max_thinking_tokens = %d;@ \ 402 system_prompt = %a;@ \ 403 append_system_prompt = %a;@ \ 404 permission_mode = %a;@ \ 405 model = %a;@ \ 406 env = %a@ \ 407 }@]" 408 Fmt.(list string) t.allowed_tools 409 Fmt.(list string) t.disallowed_tools 410 t.max_thinking_tokens 411 Fmt.(option string) t.system_prompt 412 Fmt.(option string) t.append_system_prompt 413 Fmt.(option Permissions.Mode.pp) t.permission_mode 414 Fmt.(option Model.pp) t.model 415 Fmt.(list (pair string string)) t.env 416 417let log_options t = 418 Log.debug (fun m -> m "Claude options: %a" pp t)