1use super::error::ClaudeApiError;
20use super::stream::SseParser;
21use super::types::*;
22
23pub struct MessageRequestBuilder<'a> {
32 client: &'a super::ClaudeClient,
34 model: String,
35 messages: Vec<Message>,
36 system: Option<SystemContent>,
37 max_tokens: usize,
38 temperature: Option<f32>,
39 top_p: Option<f32>,
40 top_k: Option<usize>,
41 stop_sequences: Option<Vec<String>>,
42 thinking: Option<ThinkingConfig>,
43 tools: Option<Vec<Tool>>,
44 tool_choice: Option<ToolChoice>,
45 output_config: Option<OutputConfig>,
46 metadata: Option<Metadata>,
47 stream: bool,
48 beta_headers: Vec<String>,
49}
50
51impl<'a> MessageRequestBuilder<'a> {
52 pub(crate) fn new(client: &'a super::ClaudeClient) -> Self {
54 Self {
55 client,
56 model: client.default_model.clone(),
57 messages: Vec::new(),
58 system: None,
59 max_tokens: 4096,
60 temperature: None,
61 top_p: None,
62 top_k: None,
63 stop_sequences: None,
64 thinking: None,
65 tools: None,
66 tool_choice: None,
67 output_config: None,
68 metadata: None,
69 stream: false,
70 beta_headers: Vec::new(),
71 }
72 }
73
74 pub fn model(mut self, model: &str) -> Self {
78 self.model = model.to_string();
79 self
80 }
81
82 pub fn opus(self) -> Self {
84 self.model(models::OPUS_4_6)
85 }
86
87 pub fn sonnet(self) -> Self {
89 self.model(models::SONNET_4_6)
90 }
91
92 pub fn haiku(self) -> Self {
94 self.model(models::HAIKU_4_5)
95 }
96
97 pub fn system(mut self, text: &str) -> Self {
101 self.system = Some(SystemContent::Text(text.to_string()));
102 self
103 }
104
105 pub fn system_cached(mut self, text: &str) -> Self {
107 self.system = Some(SystemContent::Blocks(vec![SystemBlock {
108 block_type: "text".to_string(),
109 text: text.to_string(),
110 cache_control: Some(CacheControl::ephemeral()),
111 }]));
112 self
113 }
114
115 pub fn user(mut self, text: &str) -> Self {
117 self.messages.push(Message::user(text));
118 self
119 }
120
121 pub fn user_with_image_base64(
123 mut self,
124 text: &str,
125 media_type: &str,
126 base64_data: &str,
127 ) -> Self {
128 self.messages.push(Message {
129 role: MessageRole::User,
130 content: MessageContent::Blocks(vec![
131 ContentBlock::Image {
132 source: ImageSource::Base64 {
133 media_type: media_type.to_string(),
134 data: base64_data.to_string(),
135 },
136 cache_control: None,
137 },
138 ContentBlock::Text {
139 text: text.to_string(),
140 cache_control: None,
141 },
142 ]),
143 });
144 self
145 }
146
147 pub fn user_with_image_url(mut self, text: &str, url: &str) -> Self {
149 self.messages.push(Message {
150 role: MessageRole::User,
151 content: MessageContent::Blocks(vec![
152 ContentBlock::Image {
153 source: ImageSource::Url {
154 url: url.to_string(),
155 },
156 cache_control: None,
157 },
158 ContentBlock::Text {
159 text: text.to_string(),
160 cache_control: None,
161 },
162 ]),
163 });
164 self
165 }
166
167 pub fn assistant(mut self, text: &str) -> Self {
169 self.messages.push(Message::assistant(text));
170 self
171 }
172
173 pub fn messages(mut self, msgs: Vec<Message>) -> Self {
175 self.messages = msgs;
176 self
177 }
178
179 pub fn tool_results(mut self, results: Vec<ContentBlock>) -> Self {
181 self.messages.push(Message {
182 role: MessageRole::User,
183 content: MessageContent::Blocks(results),
184 });
185 self
186 }
187
188 pub fn thinking_adaptive(mut self) -> Self {
193 self.thinking = Some(ThinkingConfig::Adaptive);
194 self
195 }
196
197 pub fn thinking_enabled(mut self, budget_tokens: usize) -> Self {
200 self.thinking = Some(ThinkingConfig::Enabled { budget_tokens });
201 self
202 }
203
204 pub fn thinking_disabled(mut self) -> Self {
206 self.thinking = Some(ThinkingConfig::Disabled);
207 self
208 }
209
210 pub fn effort(mut self, level: &str) -> Self {
215 let effort = match level {
216 "low" => Effort::Low,
217 "medium" => Effort::Medium,
218 "max" => Effort::Max,
219 _ => Effort::High, };
221 match self.output_config.as_mut() {
223 Some(config) => config.effort = Some(effort),
224 None => {
225 self.output_config = Some(OutputConfig {
226 effort: Some(effort),
227 format: None,
228 });
229 }
230 }
231 self
232 }
233
234 pub fn tools(mut self, tools: Vec<Tool>) -> Self {
238 self.tools = Some(tools);
239 self
240 }
241
242 pub fn tool_choice_auto(mut self) -> Self {
244 self.tool_choice = Some(ToolChoice::Auto {
245 disable_parallel_tool_use: None,
246 });
247 self
248 }
249
250 pub fn tool_choice_any(mut self) -> Self {
252 self.tool_choice = Some(ToolChoice::Any {
253 disable_parallel_tool_use: None,
254 });
255 self
256 }
257
258 pub fn tool_choice_specific(mut self, name: &str) -> Self {
260 self.tool_choice = Some(ToolChoice::Tool {
261 name: name.to_string(),
262 disable_parallel_tool_use: None,
263 });
264 self
265 }
266
267 pub fn tool_choice_none(mut self) -> Self {
269 self.tool_choice = Some(ToolChoice::None);
270 self
271 }
272
273 pub fn json_schema(mut self, schema: serde_json::Value) -> Self {
277 let format = Some(OutputFormat::JsonSchema { schema });
278 match self.output_config.as_mut() {
279 Some(config) => config.format = format,
280 None => {
281 self.output_config = Some(OutputConfig {
282 effort: None,
283 format,
284 });
285 }
286 }
287 self
288 }
289
290 pub fn max_tokens(mut self, n: usize) -> Self {
294 self.max_tokens = n;
295 self
296 }
297
298 pub fn temperature(mut self, t: f32) -> Self {
300 self.temperature = Some(t);
301 self
302 }
303
304 pub fn top_p(mut self, p: f32) -> Self {
306 self.top_p = Some(p);
307 self
308 }
309
310 pub fn top_k(mut self, k: usize) -> Self {
312 self.top_k = Some(k);
313 self
314 }
315
316 pub fn stop_sequences(mut self, seqs: Vec<String>) -> Self {
318 self.stop_sequences = Some(seqs);
319 self
320 }
321
322 pub fn user_id(mut self, id: &str) -> Self {
324 self.metadata = Some(Metadata {
325 user_id: Some(id.to_string()),
326 });
327 self
328 }
329
330 pub fn beta(mut self, header: &str) -> Self {
332 self.beta_headers.push(header.to_string());
333 self
334 }
335
336 fn build_request(&self, force_stream: bool) -> MessagesRequest {
340 MessagesRequest {
341 model: self.model.clone(),
342 messages: self.messages.clone(),
343 max_tokens: self.max_tokens,
344 system: self.system.clone(),
345 temperature: self.temperature,
346 top_p: self.top_p,
347 top_k: self.top_k,
348 stop_sequences: self.stop_sequences.clone(),
349 thinking: self.thinking.clone(),
350 tools: self.tools.clone(),
351 tool_choice: self.tool_choice.clone(),
352 output_config: self.output_config.clone(),
353 metadata: self.metadata.clone(),
354 stream: force_stream || self.stream,
355 }
356 }
357
358 pub fn build(self) -> MessagesRequest {
360 self.build_request(false)
361 }
362
363 pub async fn send(&self) -> Result<MessagesResponse, ClaudeApiError> {
365 let request = self.build_request(false);
366 let response = self
367 .client
368 .send_request(&request, &self.beta_headers)
369 .await?;
370 response
371 .json::<MessagesResponse>()
372 .await
373 .map_err(ClaudeApiError::from)
374 }
375
376 pub async fn stream(&self) -> Result<SseParser, ClaudeApiError> {
379 let request = self.build_request(true);
380 let response = self
381 .client
382 .send_request(&request, &self.beta_headers)
383 .await?;
384 Ok(SseParser::new(response))
385 }
386}