OCaml Claude SDK using Eio and Jsont
at main 171 lines 6.0 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** Object-oriented response handler with sensible defaults. 7 8 This module provides an object-oriented interface for handling response 9 events from Claude. It offers both a concrete default implementation (where 10 all methods do nothing) and an abstract base class (where all methods must 11 be implemented). 12 13 {1 Usage} 14 15 The simplest approach is to inherit from {!default} and override only the 16 methods you care about: 17 18 {[ 19 let my_handler = 20 object 21 inherit Claude.Handler.default 22 method! on_text t = print_endline (Response.Text.content t) 23 24 method! on_complete c = 25 Printf.printf "Done! Cost: $%.4f\n" 26 (Option.value ~default:0.0 (Response.Complete.total_cost_usd c)) 27 end 28 ]} 29 30 For compile-time guarantees that all events are handled, inherit from 31 {!abstract}: 32 33 {[ 34 let complete_handler = object 35 inherit Claude.Handler.abstract 36 method on_text t = (* must implement *) 37 method on_tool_use t = (* must implement *) 38 method on_tool_result t = (* must implement *) 39 method on_thinking t = (* must implement *) 40 method on_init t = (* must implement *) 41 method on_error t = (* must implement *) 42 method on_complete t = (* must implement *) 43 end 44 ]} *) 45 46(** {1 Handler Interface} *) 47 48(** The handler interface for processing response events. 49 50 Each method corresponds to a variant of {!Response.t}. Handlers can be 51 passed to {!Client.run} to process responses in an event-driven style. *) 52class type handler = object 53 method on_text : Response.Text.t -> unit 54 (** [on_text t] is called when text content is received from the assistant. *) 55 56 method on_tool_use : Response.Tool_use.t -> unit 57 (** [on_tool_use t] is called when the assistant requests a tool invocation. 58 The caller is responsible for responding with {!Client.respond_to_tool}. 59 *) 60 61 method on_tool_result : Content_block.Tool_result.t -> unit 62 (** [on_tool_result t] is called when a tool result is observed in the message 63 stream. This is typically an echo of what was sent to Claude. *) 64 65 method on_thinking : Response.Thinking.t -> unit 66 (** [on_thinking t] is called when internal reasoning content is received. *) 67 68 method on_init : Response.Init.t -> unit 69 (** [on_init t] is called when the session is initialized. This provides 70 session metadata like session_id and model. *) 71 72 method on_error : Response.Error.t -> unit 73 (** [on_error t] is called when an error occurs. Errors can come from the 74 system (e.g., CLI errors) or from the assistant (e.g., rate limits). *) 75 76 method on_complete : Response.Complete.t -> unit 77 (** [on_complete t] is called when the conversation completes. This provides 78 final metrics like duration, cost, and token usage. *) 79end 80 81(** {1 Concrete Implementations} *) 82 83class default : handler 84(** Default handler that does nothing for all events. 85 86 This is the recommended base class for most use cases. Override only the 87 methods you need: 88 89 {[ 90 let handler = 91 object 92 inherit Claude.Handler.default 93 94 method! on_text t = 95 Printf.printf "Text: %s\n" (Response.Text.content t) 96 end 97 ]} 98 99 Methods you don't override will simply be ignored, making this ideal for 100 prototyping and for cases where you only care about specific events. *) 101 102(** Abstract handler requiring all methods to be implemented. 103 104 Use this when you want compile-time guarantees that all events are handled: 105 106 {[ 107 let handler = object 108 inherit Claude.Handler.abstract 109 method on_text t = (* required *) 110 method on_tool_use t = (* required *) 111 method on_tool_result t = (* required *) 112 method on_thinking t = (* required *) 113 method on_init t = (* required *) 114 method on_error t = (* required *) 115 method on_complete t = (* required *) 116 end 117 ]} 118 119 The compiler will enforce that you implement all methods, ensuring no events 120 are silently ignored. *) 121class virtual abstract : object 122 method virtual on_text : Response.Text.t -> unit 123 (** [on_text t] must be implemented by subclasses. *) 124 125 method virtual on_tool_use : Response.Tool_use.t -> unit 126 (** [on_tool_use t] must be implemented by subclasses. *) 127 128 method virtual on_tool_result : Content_block.Tool_result.t -> unit 129 (** [on_tool_result t] must be implemented by subclasses. *) 130 131 method virtual on_thinking : Response.Thinking.t -> unit 132 (** [on_thinking t] must be implemented by subclasses. *) 133 134 method virtual on_init : Response.Init.t -> unit 135 (** [on_init t] must be implemented by subclasses. *) 136 137 method virtual on_error : Response.Error.t -> unit 138 (** [on_error t] must be implemented by subclasses. *) 139 140 method virtual on_complete : Response.Complete.t -> unit 141 (** [on_complete t] must be implemented by subclasses. *) 142end 143 144(** {1 Dispatch Functions} *) 145 146val dispatch : #handler -> Response.t -> unit 147(** [dispatch handler response] dispatches a response event to the appropriate 148 handler method based on the response type. 149 150 Example: 151 {[ 152 let handler = 153 object 154 inherit Claude.Handler.default 155 method! on_text t = print_endline (Response.Text.content t) 156 end 157 in 158 dispatch handler (Response.Text text_event) 159 ]} *) 160 161val dispatch_all : #handler -> Response.t list -> unit 162(** [dispatch_all handler responses] dispatches all response events to the 163 handler. 164 165 This is equivalent to calling [List.iter (dispatch handler) responses] but 166 may be more convenient: 167 168 {[ 169 let responses = Client.receive_all client in 170 dispatch_all handler responses 171 ]} *)