use serde::{Deserialize, Serialize};
use crate::client::Client;
use crate::error::{Error, Result};
use crate::streaming::MessageStream;
use crate::types::{
CacheControl, Message, MessageParam, Metadata, OutputConfig, SystemPrompt, ThinkingConfig,
ToolChoice, ToolUnion,
};
fn merge_extra(value: &mut serde_json::Value, extra: &serde_json::Map<String, serde_json::Value>) {
if extra.is_empty() {
return;
}
if let serde_json::Value::Object(map) = value {
for (key, entry) in extra {
map.insert(key.clone(), entry.clone());
}
}
}
#[derive(Clone, Debug, Serialize)]
#[non_exhaustive]
pub struct MessagesRequest {
pub model: String,
pub max_tokens: u32,
pub messages: Vec<MessageParam>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<SystemPrompt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_sequences: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thinking: Option<ThinkingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_config: Option<OutputConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ToolUnion>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_control: Option<CacheControl>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_k: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub container: Option<String>,
#[serde(skip)]
pub betas: Vec<String>,
#[serde(skip)]
pub extra_body: serde_json::Map<String, serde_json::Value>,
}
impl MessagesRequest {
pub fn builder() -> MessagesRequestBuilder {
MessagesRequestBuilder::default()
}
pub(crate) fn to_json_body(&self) -> Result<serde_json::Value> {
let mut value = serde_json::to_value(self)?;
merge_extra(&mut value, &self.extra_body);
Ok(value)
}
pub fn as_count_request(&self) -> CountTokensRequest {
CountTokensRequest {
model: self.model.clone(),
messages: self.messages.clone(),
system: self.system.clone(),
tools: self.tools.clone(),
thinking: self.thinking.clone(),
betas: self.betas.clone(),
extra_body: self.extra_body.clone(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct MessagesRequestBuilder {
model: Option<String>,
max_tokens: Option<u32>,
messages: Option<Vec<MessageParam>>,
system: Option<SystemPrompt>,
metadata: Option<Metadata>,
stop_sequences: Option<Vec<String>>,
stream: Option<bool>,
thinking: Option<ThinkingConfig>,
output_config: Option<OutputConfig>,
tools: Option<Vec<ToolUnion>>,
tool_choice: Option<ToolChoice>,
cache_control: Option<CacheControl>,
temperature: Option<f64>,
top_p: Option<f64>,
top_k: Option<u32>,
container: Option<String>,
betas: Vec<String>,
extra_body: serde_json::Map<String, serde_json::Value>,
}
impl MessagesRequestBuilder {
pub fn model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
pub fn max_tokens(mut self, max_tokens: u32) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub fn messages(mut self, messages: Vec<MessageParam>) -> Self {
self.messages = Some(messages);
self
}
pub fn message(mut self, message: MessageParam) -> Self {
self.messages.get_or_insert_with(Vec::new).push(message);
self
}
pub fn system(mut self, system: impl Into<SystemPrompt>) -> Self {
self.system = Some(system.into());
self
}
pub fn metadata(mut self, metadata: Metadata) -> Self {
self.metadata = Some(metadata);
self
}
pub fn stop_sequences(mut self, stop_sequences: Vec<String>) -> Self {
self.stop_sequences = Some(stop_sequences);
self
}
pub fn stream(mut self, stream: bool) -> Self {
self.stream = Some(stream);
self
}
pub fn thinking(mut self, thinking: ThinkingConfig) -> Self {
self.thinking = Some(thinking);
self
}
pub fn output_config(mut self, output_config: OutputConfig) -> Self {
self.output_config = Some(output_config);
self
}
pub fn tools(mut self, tools: Vec<ToolUnion>) -> Self {
self.tools = Some(tools);
self
}
pub fn tool(mut self, tool: impl Into<ToolUnion>) -> Self {
self.tools.get_or_insert_with(Vec::new).push(tool.into());
self
}
pub fn tool_choice(mut self, tool_choice: ToolChoice) -> Self {
self.tool_choice = Some(tool_choice);
self
}
pub fn cache_control(mut self, cache_control: CacheControl) -> Self {
self.cache_control = Some(cache_control);
self
}
pub fn temperature(mut self, temperature: f64) -> Self {
self.temperature = Some(temperature);
self
}
pub fn top_p(mut self, top_p: f64) -> Self {
self.top_p = Some(top_p);
self
}
pub fn top_k(mut self, top_k: u32) -> Self {
self.top_k = Some(top_k);
self
}
pub fn container(mut self, container: impl Into<String>) -> Self {
self.container = Some(container.into());
self
}
pub fn betas<I, S>(mut self, betas: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.betas = betas.into_iter().map(Into::into).collect();
self
}
pub fn beta(mut self, beta: impl Into<String>) -> Self {
self.betas.push(beta.into());
self
}
pub fn extra_body(mut self, extra_body: serde_json::Map<String, serde_json::Value>) -> Self {
self.extra_body = extra_body;
self
}
pub fn extra_field(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
self.extra_body.insert(key.into(), value);
self
}
pub fn build(self) -> Result<MessagesRequest> {
let model = self
.model
.filter(|model| !model.is_empty())
.ok_or_else(|| Error::Config("`model` is required".to_string()))?;
let max_tokens = self
.max_tokens
.ok_or_else(|| Error::Config("`max_tokens` is required".to_string()))?;
let messages = self
.messages
.filter(|messages| !messages.is_empty())
.ok_or_else(|| Error::Config("at least one message is required".to_string()))?;
Ok(MessagesRequest {
model,
max_tokens,
messages,
system: self.system,
metadata: self.metadata,
stop_sequences: self.stop_sequences,
stream: self.stream,
thinking: self.thinking,
output_config: self.output_config,
tools: self.tools,
tool_choice: self.tool_choice,
cache_control: self.cache_control,
temperature: self.temperature,
top_p: self.top_p,
top_k: self.top_k,
container: self.container,
betas: self.betas,
extra_body: self.extra_body,
})
}
}
#[derive(Clone, Debug, Serialize)]
pub struct CountTokensRequest {
pub model: String,
pub messages: Vec<MessageParam>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<SystemPrompt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ToolUnion>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thinking: Option<ThinkingConfig>,
#[serde(skip)]
pub betas: Vec<String>,
#[serde(skip)]
pub extra_body: serde_json::Map<String, serde_json::Value>,
}
impl CountTokensRequest {
pub fn new(model: impl Into<String>, messages: Vec<MessageParam>) -> Self {
Self {
model: model.into(),
messages,
system: None,
tools: None,
thinking: None,
betas: Vec::new(),
extra_body: serde_json::Map::new(),
}
}
pub(crate) fn to_json_body(&self) -> Result<serde_json::Value> {
let mut value = serde_json::to_value(self)?;
merge_extra(&mut value, &self.extra_body);
Ok(value)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct CountTokensResponse {
pub input_tokens: u64,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Clone, Copy, Debug)]
pub struct Messages<'a> {
client: &'a Client,
}
impl<'a> Messages<'a> {
pub(crate) fn new(client: &'a Client) -> Self {
Self { client }
}
pub async fn create(&self, request: &MessagesRequest) -> Result<Message> {
if request.stream == Some(true) {
return Err(Error::Config(
"`stream` is set to `true`; call `messages().stream(&request)` instead of \
`create()`"
.to_string(),
));
}
let body = request.to_json_body()?;
self.client
.http()
.post_json("/v1/messages", &body, &request.betas)
.await
}
pub async fn stream(&self, request: &MessagesRequest) -> Result<MessageStream> {
let mut body = request.to_json_body()?;
if let serde_json::Value::Object(map) = &mut body {
map.insert("stream".to_string(), serde_json::Value::Bool(true));
}
let response = self
.client
.http()
.post_raw("/v1/messages", &body, &request.betas)
.await?;
Ok(MessageStream::new(response))
}
pub async fn count_tokens(&self, request: &CountTokensRequest) -> Result<CountTokensResponse> {
let body = request.to_json_body()?;
self.client
.http()
.post_json("/v1/messages/count_tokens", &body, &request.betas)
.await
}
}