use super::common::{CommonParams, FinishReason, HttpConfig, ProviderParams, Usage};
use super::tools::{Tool, ToolCall};
use super::web_search::WebSearchConfig;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum MessageRole {
System,
User,
Assistant,
Developer, Tool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum MessageContent {
Text(String),
MultiModal(Vec<ContentPart>),
}
impl MessageContent {
pub fn text(&self) -> Option<&str> {
match self {
MessageContent::Text(text) => Some(text),
MessageContent::MultiModal(parts) => {
for part in parts {
if let ContentPart::Text { text } = part {
return Some(text);
}
}
None
}
}
}
pub fn all_text(&self) -> String {
match self {
MessageContent::Text(text) => text.clone(),
MessageContent::MultiModal(parts) => {
let mut result = String::new();
for part in parts {
if let ContentPart::Text { text } = part {
if !result.is_empty() {
result.push(' ');
}
result.push_str(text);
}
}
result
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ContentPart {
Text {
text: String,
},
Image {
image_url: String,
detail: Option<String>,
},
Audio {
audio_url: String,
format: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CacheControl {
Ephemeral,
Persistent { ttl: Option<std::time::Duration> },
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MessageMetadata {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timestamp: Option<chrono::DateTime<chrono::Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache_control: Option<CacheControl>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub custom: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
pub role: MessageRole,
pub content: MessageContent,
#[serde(default)]
pub metadata: MessageMetadata,
pub tool_calls: Option<Vec<ToolCall>>,
pub tool_call_id: Option<String>,
}
impl ChatMessage {
pub fn user<S: Into<String>>(content: S) -> ChatMessageBuilder {
ChatMessageBuilder::user(content)
}
pub fn system<S: Into<String>>(content: S) -> ChatMessageBuilder {
ChatMessageBuilder::system(content)
}
pub fn assistant<S: Into<String>>(content: S) -> ChatMessageBuilder {
ChatMessageBuilder::assistant(content)
}
pub fn developer<S: Into<String>>(content: S) -> ChatMessageBuilder {
ChatMessageBuilder::developer(content)
}
pub fn tool<S: Into<String>>(content: S, tool_call_id: S) -> ChatMessageBuilder {
ChatMessageBuilder::tool(content, tool_call_id)
}
pub fn content_text(&self) -> Option<&str> {
match &self.content {
MessageContent::Text(text) => Some(text),
MessageContent::MultiModal(parts) => parts.iter().find_map(|part| {
if let ContentPart::Text { text } = part {
Some(text.as_str())
} else {
None
}
}),
}
}
pub fn user_static(content: &'static str) -> ChatMessageBuilder {
ChatMessageBuilder::user(content)
}
pub fn assistant_static(content: &'static str) -> ChatMessageBuilder {
ChatMessageBuilder::assistant(content)
}
pub fn system_static(content: &'static str) -> ChatMessageBuilder {
ChatMessageBuilder::system(content)
}
pub fn user_with_capacity(content: String, _capacity_hint: usize) -> ChatMessageBuilder {
ChatMessageBuilder::user(content)
}
pub const fn is_empty(&self) -> bool {
match &self.content {
MessageContent::Text(text) => text.is_empty(),
MessageContent::MultiModal(parts) => parts.is_empty(),
}
}
pub fn content_length(&self) -> usize {
match &self.content {
MessageContent::Text(text) => text.len(),
MessageContent::MultiModal(parts) => parts
.iter()
.map(|part| match part {
ContentPart::Text { text } => text.len(),
ContentPart::Image { image_url, .. } => image_url.len(),
ContentPart::Audio { audio_url, .. } => audio_url.len(),
})
.sum(),
}
}
pub fn metadata(&self) -> &MessageMetadata {
&self.metadata
}
pub fn metadata_mut(&mut self) -> &mut MessageMetadata {
&mut self.metadata
}
pub fn has_metadata(&self) -> bool {
self.metadata.id.is_some()
|| self.metadata.timestamp.is_some()
|| self.metadata.cache_control.is_some()
|| !self.metadata.custom.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct ChatMessageBuilder {
role: MessageRole,
content: Option<MessageContent>,
metadata: MessageMetadata,
tool_calls: Option<Vec<ToolCall>>,
tool_call_id: Option<String>,
}
impl ChatMessageBuilder {
pub fn user<S: Into<String>>(content: S) -> Self {
Self {
role: MessageRole::User,
content: Some(MessageContent::Text(content.into())),
metadata: MessageMetadata::default(),
tool_calls: None,
tool_call_id: None,
}
}
pub fn user_with_capacity(content: String, _capacity_hint: usize) -> Self {
Self {
role: MessageRole::User,
content: Some(MessageContent::Text(content)),
metadata: MessageMetadata::default(),
tool_calls: None,
tool_call_id: None,
}
}
pub fn system<S: Into<String>>(content: S) -> Self {
Self {
role: MessageRole::System,
content: Some(MessageContent::Text(content.into())),
metadata: MessageMetadata::default(),
tool_calls: None,
tool_call_id: None,
}
}
pub fn assistant<S: Into<String>>(content: S) -> Self {
Self {
role: MessageRole::Assistant,
content: Some(MessageContent::Text(content.into())),
metadata: MessageMetadata::default(),
tool_calls: None,
tool_call_id: None,
}
}
pub fn developer<S: Into<String>>(content: S) -> Self {
Self {
role: MessageRole::Developer,
content: Some(MessageContent::Text(content.into())),
metadata: MessageMetadata::default(),
tool_calls: None,
tool_call_id: None,
}
}
pub fn tool<S: Into<String>>(content: S, tool_call_id: S) -> Self {
Self {
role: MessageRole::Tool,
content: Some(MessageContent::Text(content.into())),
metadata: MessageMetadata::default(),
tool_calls: None,
tool_call_id: Some(tool_call_id.into()),
}
}
pub const fn cache_control(mut self, cache: CacheControl) -> Self {
self.metadata.cache_control = Some(cache);
self
}
pub fn with_image(mut self, image_url: String, detail: Option<String>) -> Self {
let image_part = ContentPart::Image { image_url, detail };
match self.content {
Some(MessageContent::Text(text)) => {
self.content = Some(MessageContent::MultiModal(vec![
ContentPart::Text { text },
image_part,
]));
}
Some(MessageContent::MultiModal(ref mut parts)) => {
parts.push(image_part);
}
None => {
self.content = Some(MessageContent::MultiModal(vec![image_part]));
}
}
self
}
pub fn with_tool_calls(mut self, tool_calls: Vec<ToolCall>) -> Self {
self.tool_calls = Some(tool_calls);
self
}
pub fn build(self) -> ChatMessage {
ChatMessage {
role: self.role,
content: self.content.unwrap_or(MessageContent::Text(String::new())),
metadata: self.metadata,
tool_calls: self.tool_calls,
tool_call_id: self.tool_call_id,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ChatRequest {
pub messages: Vec<ChatMessage>,
pub tools: Option<Vec<Tool>>,
pub common_params: CommonParams,
pub provider_params: Option<ProviderParams>,
pub http_config: Option<HttpConfig>,
pub web_search: Option<WebSearchConfig>,
pub stream: bool,
}
impl ChatRequest {
pub fn new(messages: Vec<ChatMessage>) -> Self {
Self {
messages,
tools: None,
common_params: CommonParams::default(),
provider_params: None,
http_config: None,
web_search: None,
stream: false,
}
}
pub fn builder() -> ChatRequestBuilder {
ChatRequestBuilder::new()
}
pub fn with_message(mut self, message: ChatMessage) -> Self {
self.messages.push(message);
self
}
pub fn with_messages(mut self, messages: Vec<ChatMessage>) -> Self {
self.messages.extend(messages);
self
}
pub fn with_tools(mut self, tools: Vec<Tool>) -> Self {
self.tools = Some(tools);
self
}
pub const fn with_streaming(mut self, stream: bool) -> Self {
self.stream = stream;
self
}
pub fn with_common_params(mut self, params: CommonParams) -> Self {
self.common_params = params;
self
}
pub fn with_model_params(mut self, params: CommonParams) -> Self {
self.common_params = params;
self
}
pub fn with_provider_params(mut self, params: ProviderParams) -> Self {
self.provider_params = Some(params);
self
}
pub fn with_http_config(mut self, config: HttpConfig) -> Self {
self.http_config = Some(config);
self
}
pub fn with_web_search(mut self, config: WebSearchConfig) -> Self {
self.web_search = Some(config);
self
}
}
#[derive(Debug, Clone)]
pub struct ChatRequestBuilder {
messages: Vec<ChatMessage>,
tools: Option<Vec<Tool>>,
common_params: CommonParams,
provider_params: Option<ProviderParams>,
http_config: Option<HttpConfig>,
web_search: Option<WebSearchConfig>,
stream: bool,
}
impl ChatRequestBuilder {
pub fn new() -> Self {
Self {
messages: Vec::new(),
tools: None,
common_params: CommonParams::default(),
provider_params: None,
http_config: None,
web_search: None,
stream: false,
}
}
pub fn message(mut self, message: ChatMessage) -> Self {
self.messages.push(message);
self
}
pub fn messages(mut self, messages: Vec<ChatMessage>) -> Self {
self.messages.extend(messages);
self
}
pub fn tools(mut self, tools: Vec<Tool>) -> Self {
self.tools = Some(tools);
self
}
pub const fn stream(mut self, stream: bool) -> Self {
self.stream = stream;
self
}
pub fn common_params(mut self, params: CommonParams) -> Self {
self.common_params = params;
self
}
pub fn model_params(mut self, params: CommonParams) -> Self {
self.common_params = params;
self
}
pub fn provider_params(mut self, params: ProviderParams) -> Self {
self.provider_params = Some(params);
self
}
pub fn http_config(mut self, config: HttpConfig) -> Self {
self.http_config = Some(config);
self
}
pub fn web_search(mut self, config: WebSearchConfig) -> Self {
self.web_search = Some(config);
self
}
pub fn build(self) -> ChatRequest {
ChatRequest {
messages: self.messages,
tools: self.tools,
common_params: self.common_params,
provider_params: self.provider_params,
http_config: self.http_config,
web_search: self.web_search,
stream: self.stream,
}
}
}
impl Default for ChatRequestBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatResponse {
pub id: Option<String>,
pub content: MessageContent,
pub model: Option<String>,
pub usage: Option<Usage>,
pub finish_reason: Option<FinishReason>,
pub tool_calls: Option<Vec<ToolCall>>,
pub thinking: Option<String>,
pub metadata: HashMap<String, serde_json::Value>,
}
impl ChatResponse {
pub fn new(content: MessageContent) -> Self {
Self {
id: None,
content,
model: None,
usage: None,
finish_reason: None,
tool_calls: None,
thinking: None,
metadata: HashMap::new(),
}
}
pub fn content_text(&self) -> Option<&str> {
self.content.text()
}
pub fn text(&self) -> Option<String> {
Some(self.content.all_text())
}
pub fn has_tool_calls(&self) -> bool {
self.tool_calls
.as_ref()
.is_some_and(|calls| !calls.is_empty())
}
pub const fn get_tool_calls(&self) -> Option<&Vec<ToolCall>> {
self.tool_calls.as_ref()
}
pub fn has_thinking(&self) -> bool {
self.thinking.as_ref().is_some_and(|t| !t.is_empty())
}
pub fn get_thinking(&self) -> Option<&str> {
self.thinking.as_deref()
}
pub fn thinking_or_empty(&self) -> &str {
self.thinking.as_deref().unwrap_or("")
}
}