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
6open Eio.Std
7
8let src = Logs.Src.create "camel_jokes" ~doc:"Camel joke competition"
9
10module Log = (val Logs.src_log src : Logs.LOG)
11
12let process_claude_response client name =
13 Log.info (fun m -> m "=== %s's Response ===" name);
14 let responses = Claude.Client.receive_all client in
15 List.iter
16 (fun resp ->
17 match resp with
18 | Claude.Response.Text t ->
19 let text = Claude.Response.Text.content t in
20 Log.app (fun m -> m "%s: %s" name text)
21 | Claude.Response.Tool_use t ->
22 Log.debug (fun m ->
23 m "%s using tool: %s" name (Claude.Response.Tool_use.name t))
24 | Claude.Response.Thinking t ->
25 Log.debug (fun m ->
26 m "%s thinking: %s" name (Claude.Response.Thinking.content t))
27 | Claude.Response.Complete c ->
28 (if Claude.Response.Complete.total_cost_usd c <> None then
29 let cost =
30 Option.get (Claude.Response.Complete.total_cost_usd c)
31 in
32 Log.info (fun m -> m "%s's joke cost: $%.6f" name cost));
33 Log.debug (fun m ->
34 m "%s session: %s, duration: %dms" name
35 (Claude.Response.Complete.session_id c)
36 (Claude.Response.Complete.duration_ms c))
37 | Claude.Response.Error e ->
38 Log.err (fun m ->
39 m "Error from %s: %s" name (Claude.Response.Error.message e))
40 | Claude.Response.Init _ ->
41 (* Init messages are already logged by the library *)
42 ()
43 | Claude.Response.Tool_result _ ->
44 (* Tool results are user messages, skip *)
45 ())
46 responses
47
48let run_claude ~sw ~env name prompt =
49 Log.info (fun m -> m "🐪 Starting %s..." name);
50 let options =
51 Claude.Options.default
52 |> Claude.Options.with_model (Claude.Model.of_string "sonnet")
53 |> Claude.Options.with_allowed_tools []
54 in
55
56 let client =
57 Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
58 ~clock:env#clock ()
59 in
60
61 Claude.Client.query client prompt;
62 process_claude_response client name
63
64let main ~env =
65 Switch.run @@ fun sw ->
66 Log.app (fun m -> m "🐪 Starting the Great Camel Joke Competition! 🐪");
67 Log.app (fun m -> m "================================================\n");
68
69 let prompts =
70 [
71 ( "Claude 1",
72 "Tell me a short, funny joke about camels! Make it original and clever."
73 );
74 ( "Claude 2",
75 "Give me your best camel joke - something witty and unexpected!" );
76 ("Claude 3", "Share a hilarious camel joke that will make everyone laugh!");
77 ]
78 in
79
80 (* Run all three Claudes concurrently *)
81 Fiber.all
82 (List.map
83 (fun (name, prompt) -> fun () -> run_claude ~sw ~env name prompt)
84 prompts);
85
86 Log.app (fun m -> m "\n================================================");
87 Log.app (fun m -> m "🎉 The Camel Joke Competition is complete! 🎉")
88
89(* Command-line interface *)
90open Cmdliner
91
92let main_term env =
93 let setup_log style_renderer level =
94 Fmt_tty.setup_std_outputs ?style_renderer ();
95 Logs.set_level level;
96 Logs.set_reporter (Logs_fmt.reporter ());
97 (* Set default to App level if not specified *)
98 if level = None then Logs.set_level (Some Logs.App);
99 (* Enable debug for Client module if in debug mode *)
100 if level = Some Logs.Debug then
101 Logs.Src.set_level Claude.Client.src (Some Logs.Debug)
102 in
103 let run style level =
104 setup_log style level;
105 main ~env
106 in
107 Term.(const run $ Fmt_cli.style_renderer () $ Logs_cli.level ())
108
109let cmd env =
110 let doc = "Run the Great Camel Joke Competition using Claude" in
111 let man =
112 [
113 `S Manpage.s_description;
114 `P
115 "This program runs three concurrent Claude instances to generate camel \
116 jokes.";
117 `P "Use $(b,-v) or $(b,--verbosity=info) to see RPC message traffic.";
118 `P
119 "Use $(b,-vv) or $(b,--verbosity=debug) to see all internal operations.";
120 `S Manpage.s_examples;
121 `P "Run with normal output:";
122 `Pre " $(mname)";
123 `P "Run with info-level logging (RPC traffic):";
124 `Pre " $(mname) -v";
125 `Pre " $(mname) --verbosity=info";
126 `P "Run with debug logging (all operations):";
127 `Pre " $(mname) -vv";
128 `Pre " $(mname) --verbosity=debug";
129 `P "Enable debug for specific modules:";
130 `Pre " LOGS='claude.transport=debug' $(mname)";
131 `Pre " LOGS='claude.message=info,camel_jokes=debug' $(mname)";
132 `S Manpage.s_bugs;
133 `P "Report bugs at https://github.com/your-repo/issues";
134 ]
135 in
136 let info = Cmd.info "camel_jokes" ~version:"1.0" ~doc ~man in
137 Cmd.v info (main_term env)
138
139let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))