OCaml Claude SDK using Eio and Jsont
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6let src = Logs.Src.create "claudeio.options" ~doc:"Claude configuration options"
7
8module Log = (val Logs.src_log src : Logs.LOG)
9
10type t = {
11 allowed_tools : string list;
12 disallowed_tools : string list;
13 max_thinking_tokens : int;
14 system_prompt : string option;
15 append_system_prompt : string option;
16 permission_mode : Permissions.Mode.t option;
17 permission_callback : Permissions.callback option;
18 model : Proto.Model.t option;
19 cwd : Eio.Fs.dir_ty Eio.Path.t option;
20 env : (string * string) list;
21 continue_conversation : bool;
22 resume : string option;
23 max_turns : int option;
24 permission_prompt_tool_name : string option;
25 settings : string option;
26 add_dirs : string list;
27 extra_args : (string * string option) list;
28 debug_stderr : Eio.Flow.sink_ty Eio.Flow.sink option;
29 hooks : Hooks.t option;
30 max_budget_usd : float option;
31 fallback_model : Proto.Model.t option;
32 setting_sources : Proto.Options.setting_source list option;
33 max_buffer_size : int option;
34 user : string option;
35 output_format : Proto.Structured_output.t option;
36 mcp_servers : (string * Mcp_server.t) list;
37}
38
39let default =
40 {
41 allowed_tools = [];
42 disallowed_tools = [];
43 max_thinking_tokens = 8000;
44 system_prompt = None;
45 append_system_prompt = None;
46 permission_mode = None;
47 permission_callback = Some Permissions.default_allow;
48 model = None;
49 cwd = None;
50 env = [];
51 continue_conversation = false;
52 resume = None;
53 max_turns = None;
54 permission_prompt_tool_name = None;
55 settings = None;
56 add_dirs = [];
57 extra_args = [];
58 debug_stderr = None;
59 hooks = None;
60 max_budget_usd = None;
61 fallback_model = None;
62 setting_sources = None;
63 max_buffer_size = None;
64 user = None;
65 output_format = None;
66 mcp_servers = [];
67 }
68
69(* Accessors *)
70let allowed_tools t = t.allowed_tools
71let disallowed_tools t = t.disallowed_tools
72let max_thinking_tokens t = t.max_thinking_tokens
73let system_prompt t = t.system_prompt
74let append_system_prompt t = t.append_system_prompt
75let permission_mode t = t.permission_mode
76let permission_callback t = t.permission_callback
77let model t = t.model
78let cwd t = t.cwd
79let env t = t.env
80let continue_conversation t = t.continue_conversation
81let resume t = t.resume
82let max_turns t = t.max_turns
83let permission_prompt_tool_name t = t.permission_prompt_tool_name
84let settings t = t.settings
85let add_dirs t = t.add_dirs
86let extra_args t = t.extra_args
87let debug_stderr t = t.debug_stderr
88let hooks t = t.hooks
89let max_budget_usd t = t.max_budget_usd
90let fallback_model t = t.fallback_model
91let setting_sources t = t.setting_sources
92let max_buffer_size t = t.max_buffer_size
93let user t = t.user
94let output_format t = t.output_format
95let mcp_servers t = t.mcp_servers
96
97(* Builders *)
98let with_allowed_tools tools t = { t with allowed_tools = tools }
99let with_disallowed_tools tools t = { t with disallowed_tools = tools }
100let with_max_thinking_tokens tokens t = { t with max_thinking_tokens = tokens }
101let with_system_prompt prompt t = { t with system_prompt = Some prompt }
102
103let with_append_system_prompt prompt t =
104 { t with append_system_prompt = Some prompt }
105
106let with_permission_mode mode t = { t with permission_mode = Some mode }
107
108let with_permission_callback callback t =
109 { t with permission_callback = Some callback }
110
111let with_model model t = { t with model = Some model }
112let with_cwd cwd t = { t with cwd = Some (cwd :> Eio.Fs.dir_ty Eio.Path.t) }
113let with_env env t = { t with env }
114
115let with_continue_conversation continue t =
116 { t with continue_conversation = continue }
117
118let with_resume session_id t = { t with resume = Some session_id }
119let with_max_turns turns t = { t with max_turns = Some turns }
120
121let with_permission_prompt_tool_name tool t =
122 { t with permission_prompt_tool_name = Some tool }
123
124let with_settings path t = { t with settings = Some path }
125let with_add_dirs dirs t = { t with add_dirs = dirs }
126let with_extra_args args t = { t with extra_args = args }
127
128let with_debug_stderr sink t =
129 { t with debug_stderr = Some (sink :> Eio.Flow.sink_ty Eio.Flow.sink) }
130
131let with_hooks hooks t = { t with hooks = Some hooks }
132let with_max_budget_usd budget t = { t with max_budget_usd = Some budget }
133let with_fallback_model model t = { t with fallback_model = Some model }
134let with_no_settings t = { t with setting_sources = Some [] }
135let with_max_buffer_size size t = { t with max_buffer_size = Some size }
136let with_user user t = { t with user = Some user }
137let with_output_format format t = { t with output_format = Some format }
138
139let with_mcp_server ~name server t =
140 { t with mcp_servers = (name, server) :: t.mcp_servers }
141
142let log_options t =
143 Log.debug (fun m ->
144 m "Options: model=%s fallback=%s max_thinking_tokens=%d max_budget=%s"
145 (match t.model with
146 | None -> "default"
147 | Some m -> Proto.Model.to_string m)
148 (match t.fallback_model with
149 | None -> "none"
150 | Some m -> Proto.Model.to_string m)
151 t.max_thinking_tokens
152 (match t.max_budget_usd with
153 | None -> "unlimited"
154 | Some b -> Printf.sprintf "$%.2f" b))
155
156module Advanced = struct
157 let to_wire (t : t) : Proto.Options.t =
158 let base = Proto.Options.empty in
159 let base = Proto.Options.with_allowed_tools t.allowed_tools base in
160 let base = Proto.Options.with_disallowed_tools t.disallowed_tools base in
161 let base =
162 Proto.Options.with_max_thinking_tokens t.max_thinking_tokens base
163 in
164 let base =
165 match t.system_prompt with
166 | None -> base
167 | Some p -> Proto.Options.with_system_prompt p base
168 in
169 let base =
170 match t.append_system_prompt with
171 | None -> base
172 | Some p -> Proto.Options.with_append_system_prompt p base
173 in
174 let base =
175 match t.permission_mode with
176 | None -> base
177 | Some m ->
178 Proto.Options.with_permission_mode (Permissions.Mode.to_proto m) base
179 in
180 let base =
181 match t.model with
182 | None -> base
183 | Some m -> Proto.Options.with_model m base
184 in
185 let base =
186 Proto.Options.with_continue_conversation t.continue_conversation base
187 in
188 let base =
189 match t.resume with
190 | None -> base
191 | Some r -> Proto.Options.with_resume r base
192 in
193 let base =
194 match t.max_turns with
195 | None -> base
196 | Some turns -> Proto.Options.with_max_turns turns base
197 in
198 let base =
199 match t.permission_prompt_tool_name with
200 | None -> base
201 | Some tool -> Proto.Options.with_permission_prompt_tool_name tool base
202 in
203 let base =
204 match t.settings with
205 | None -> base
206 | Some s -> Proto.Options.with_settings s base
207 in
208 let base = Proto.Options.with_add_dirs t.add_dirs base in
209 let base =
210 match t.max_budget_usd with
211 | None -> base
212 | Some b -> Proto.Options.with_max_budget_usd b base
213 in
214 let base =
215 match t.fallback_model with
216 | None -> base
217 | Some m -> Proto.Options.with_fallback_model m base
218 in
219 let base =
220 match t.setting_sources with
221 | None -> base
222 | Some sources -> Proto.Options.with_setting_sources sources base
223 in
224 let base =
225 match t.max_buffer_size with
226 | None -> base
227 | Some size -> Proto.Options.with_max_buffer_size size base
228 in
229 let base =
230 match t.user with
231 | None -> base
232 | Some u -> Proto.Options.with_user u base
233 in
234 let base =
235 match t.output_format with
236 | None -> base
237 | Some format -> Proto.Options.with_output_format format base
238 in
239 base
240end