use super::error::ClaudeApiError;
use super::stream::SseParser;
use super::types::*;
pub struct MessageRequestBuilder<'a> {
client: &'a super::ClaudeClient,
model: String,
messages: Vec<Message>,
system: Option<SystemContent>,
max_tokens: usize,
temperature: Option<f32>,
top_p: Option<f32>,
top_k: Option<usize>,
stop_sequences: Option<Vec<String>>,
thinking: Option<ThinkingConfig>,
tools: Option<Vec<Tool>>,
tool_choice: Option<ToolChoice>,
output_config: Option<OutputConfig>,
metadata: Option<Metadata>,
stream: bool,
beta_headers: Vec<String>,
}
impl<'a> MessageRequestBuilder<'a> {
pub(crate) fn new(client: &'a super::ClaudeClient) -> Self {
Self {
client,
model: client.default_model.clone(),
messages: Vec::new(),
system: None,
max_tokens: 4096,
temperature: None,
top_p: None,
top_k: None,
stop_sequences: None,
thinking: None,
tools: None,
tool_choice: None,
output_config: None,
metadata: None,
stream: false,
beta_headers: Vec::new(),
}
}
pub fn model(mut self, model: &str) -> Self {
self.model = model.to_string();
self
}
pub fn opus(self) -> Self {
self.model(models::OPUS_4_6)
}
pub fn sonnet(self) -> Self {
self.model(models::SONNET_4_6)
}
pub fn haiku(self) -> Self {
self.model(models::HAIKU_4_5)
}
pub fn system(mut self, text: &str) -> Self {
self.system = Some(SystemContent::Text(text.to_string()));
self
}
pub fn system_cached(mut self, text: &str) -> Self {
self.system = Some(SystemContent::Blocks(vec![SystemBlock {
block_type: "text".to_string(),
text: text.to_string(),
cache_control: Some(CacheControl::ephemeral()),
}]));
self
}
pub fn user(mut self, text: &str) -> Self {
self.messages.push(Message::user(text));
self
}
pub fn user_with_image_base64(
mut self,
text: &str,
media_type: &str,
base64_data: &str,
) -> Self {
self.messages.push(Message {
role: MessageRole::User,
content: MessageContent::Blocks(vec![
ContentBlock::Image {
source: ImageSource::Base64 {
media_type: media_type.to_string(),
data: base64_data.to_string(),
},
cache_control: None,
},
ContentBlock::Text {
text: text.to_string(),
cache_control: None,
},
]),
});
self
}
pub fn user_with_image_url(mut self, text: &str, url: &str) -> Self {
self.messages.push(Message {
role: MessageRole::User,
content: MessageContent::Blocks(vec![
ContentBlock::Image {
source: ImageSource::Url {
url: url.to_string(),
},
cache_control: None,
},
ContentBlock::Text {
text: text.to_string(),
cache_control: None,
},
]),
});
self
}
pub fn assistant(mut self, text: &str) -> Self {
self.messages.push(Message::assistant(text));
self
}
pub fn messages(mut self, msgs: Vec<Message>) -> Self {
self.messages = msgs;
self
}
pub fn tool_results(mut self, results: Vec<ContentBlock>) -> Self {
self.messages.push(Message {
role: MessageRole::User,
content: MessageContent::Blocks(results),
});
self
}
pub fn thinking_adaptive(mut self) -> Self {
self.thinking = Some(ThinkingConfig::Adaptive);
self
}
pub fn thinking_enabled(mut self, budget_tokens: usize) -> Self {
self.thinking = Some(ThinkingConfig::Enabled { budget_tokens });
self
}
pub fn thinking_disabled(mut self) -> Self {
self.thinking = Some(ThinkingConfig::Disabled);
self
}
pub fn effort(mut self, level: &str) -> Self {
let effort = match level {
"low" => Effort::Low,
"medium" => Effort::Medium,
"max" => Effort::Max,
_ => Effort::High, };
match self.output_config.as_mut() {
Some(config) => config.effort = Some(effort),
None => {
self.output_config = Some(OutputConfig {
effort: Some(effort),
format: None,
});
}
}
self
}
pub fn tools(mut self, tools: Vec<Tool>) -> Self {
self.tools = Some(tools);
self
}
pub fn tool_choice_auto(mut self) -> Self {
self.tool_choice = Some(ToolChoice::Auto {
disable_parallel_tool_use: None,
});
self
}
pub fn tool_choice_any(mut self) -> Self {
self.tool_choice = Some(ToolChoice::Any {
disable_parallel_tool_use: None,
});
self
}
pub fn tool_choice_specific(mut self, name: &str) -> Self {
self.tool_choice = Some(ToolChoice::Tool {
name: name.to_string(),
disable_parallel_tool_use: None,
});
self
}
pub fn tool_choice_none(mut self) -> Self {
self.tool_choice = Some(ToolChoice::None);
self
}
pub fn json_schema(mut self, schema: serde_json::Value) -> Self {
let format = Some(OutputFormat::JsonSchema { schema });
match self.output_config.as_mut() {
Some(config) => config.format = format,
None => {
self.output_config = Some(OutputConfig {
effort: None,
format,
});
}
}
self
}
pub fn max_tokens(mut self, n: usize) -> Self {
self.max_tokens = n;
self
}
pub fn temperature(mut self, t: f32) -> Self {
self.temperature = Some(t);
self
}
pub fn top_p(mut self, p: f32) -> Self {
self.top_p = Some(p);
self
}
pub fn top_k(mut self, k: usize) -> Self {
self.top_k = Some(k);
self
}
pub fn stop_sequences(mut self, seqs: Vec<String>) -> Self {
self.stop_sequences = Some(seqs);
self
}
pub fn user_id(mut self, id: &str) -> Self {
self.metadata = Some(Metadata {
user_id: Some(id.to_string()),
});
self
}
pub fn beta(mut self, header: &str) -> Self {
self.beta_headers.push(header.to_string());
self
}
fn build_request(&self, force_stream: bool) -> MessagesRequest {
MessagesRequest {
model: self.model.clone(),
messages: self.messages.clone(),
max_tokens: self.max_tokens,
system: self.system.clone(),
temperature: self.temperature,
top_p: self.top_p,
top_k: self.top_k,
stop_sequences: self.stop_sequences.clone(),
thinking: self.thinking.clone(),
tools: self.tools.clone(),
tool_choice: self.tool_choice.clone(),
output_config: self.output_config.clone(),
metadata: self.metadata.clone(),
stream: force_stream || self.stream,
}
}
pub fn build(self) -> MessagesRequest {
self.build_request(false)
}
pub async fn send(&self) -> Result<MessagesResponse, ClaudeApiError> {
let request = self.build_request(false);
let response = self
.client
.send_request(&request, &self.beta_headers)
.await?;
response
.json::<MessagesResponse>()
.await
.map_err(ClaudeApiError::from)
}
pub async fn stream(&self) -> Result<SseParser, ClaudeApiError> {
let request = self.build_request(true);
let response = self
.client
.send_request(&request, &self.beta_headers)
.await?;
Ok(SseParser::new(response))
}
}