OCaml Claude SDK using Eio and Jsont
at main 139 lines 5.0 kB view raw
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))