use crate::models::tool::{ToolCall, ToolCallChunk};
use crate::types::ids::ToolCallId;
use crate::types::status::StreamingStatus;
use serde::de::Error as DeError;
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ContentType {
Text,
ImageUrl,
AudioUrl,
FileUrl,
}
impl std::fmt::Display for ContentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ContentType::Text => write!(f, "text"),
ContentType::ImageUrl => write!(f, "image_url"),
ContentType::AudioUrl => write!(f, "audio_url"),
ContentType::FileUrl => write!(f, "file_url"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ChatRole {
User,
Assistant,
System,
Tool,
}
impl std::fmt::Display for ChatRole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ChatRole::User => write!(f, "user"),
ChatRole::Assistant => write!(f, "assistant"),
ChatRole::System => write!(f, "system"),
ChatRole::Tool => write!(f, "tool"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum StopSequence {
Single(String),
Multiple(Vec<String>),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PredictionConfig {
#[serde(rename = "type")]
pub prediction_type: String, pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum VerbosityLevel {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum RouteStrategy {
Fallback,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ImageDetail {
Auto,
Low,
High,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TextContent {
#[serde(rename = "type")]
pub content_type: ContentType,
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ImageUrl {
pub url: String, #[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<ImageDetail>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ImageContent {
#[serde(rename = "type")]
pub content_type: ContentType,
pub image_url: ImageUrl,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AudioUrl {
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AudioContent {
#[serde(rename = "type")]
pub content_type: ContentType,
pub audio_url: AudioUrl,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FileUrl {
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FileContent {
#[serde(rename = "type")]
pub content_type: ContentType,
pub file_url: FileUrl,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum ContentPart {
Text(TextContent),
Image(ImageContent),
Audio(AudioContent),
File(FileContent),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum MessageContent {
Text(String),
Parts(Vec<ContentPart>),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Message {
pub role: ChatRole,
pub content: MessageContent,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<ToolCallId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCall>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_details: Option<Vec<ReasoningDetail>>,
}
impl Default for Message {
fn default() -> Self {
Self {
role: ChatRole::User,
content: MessageContent::Text("".to_string()),
name: None,
tool_call_id: None,
tool_calls: None,
reasoning: None,
reasoning_details: None,
}
}
}
impl Message {
pub fn text(role: ChatRole, content: impl Into<String>) -> Self {
Self {
role,
content: MessageContent::Text(content.into()),
name: None,
tool_calls: None,
tool_call_id: None,
reasoning: None,
reasoning_details: None,
}
}
pub fn text_with_name(
role: ChatRole,
content: impl Into<String>,
name: impl Into<String>,
) -> Self {
Self {
role,
content: MessageContent::Text(content.into()),
name: Some(name.into()),
tool_calls: None,
tool_call_id: None,
reasoning: None,
reasoning_details: None,
}
}
pub fn multimodal(role: ChatRole, parts: Vec<ContentPart>) -> Self {
Self {
role,
content: MessageContent::Parts(parts),
name: None,
tool_calls: None,
tool_call_id: None,
reasoning: None,
reasoning_details: None,
}
}
pub fn tool(content: impl Into<String>, tool_call_id: impl Into<ToolCallId>) -> Self {
Self {
role: ChatRole::Tool,
content: MessageContent::Text(content.into()),
name: None,
tool_calls: None,
tool_call_id: Some(tool_call_id.into()),
reasoning: None,
reasoning_details: None,
}
}
pub fn assistant_with_tools(
content: Option<impl Into<String>>,
tool_calls: Vec<ToolCall>,
) -> Self {
Self {
role: ChatRole::Assistant,
content: content
.map(|c| MessageContent::Text(c.into()))
.unwrap_or(MessageContent::Text("".to_string())),
name: None,
tool_calls: Some(tool_calls),
tool_call_id: None,
reasoning: None,
reasoning_details: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DebugConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub echo_upstream_body: Option<bool>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Plugin {
pub id: String,
pub enabled: Option<bool>,
pub config: Option<serde_json::Value>,
}
impl Serialize for Plugin {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("id", &self.id)?;
if let Some(enabled) = self.enabled {
map.serialize_entry("enabled", &enabled)?;
}
if let Some(config) = &self.config {
match config {
serde_json::Value::Object(object) => {
for (key, value) in object {
map.serialize_entry(key, value)?;
}
}
other => {
map.serialize_entry("config", other)?;
}
}
}
map.end()
}
}
impl<'de> Deserialize<'de> for Plugin {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let mut raw = serde_json::Map::<String, serde_json::Value>::deserialize(deserializer)?;
let id = match raw.remove("id") {
Some(serde_json::Value::String(id)) => id,
Some(other) => {
return Err(D::Error::custom(format!(
"plugin id must be a string, got {other}"
)))
}
None => return Err(D::Error::missing_field("id")),
};
let enabled = match raw.remove("enabled") {
Some(value) => {
serde_json::from_value::<Option<bool>>(value).map_err(D::Error::custom)?
}
None => None,
};
let legacy_config = raw.remove("config");
let flattened_config = if raw.is_empty() {
None
} else {
Some(serde_json::Value::Object(raw))
};
let config = match (legacy_config, flattened_config) {
(None, None) => None,
(Some(config), None) => Some(config),
(None, Some(config)) => Some(config),
(
Some(serde_json::Value::Object(mut legacy)),
Some(serde_json::Value::Object(flattened)),
) => {
legacy.extend(flattened);
Some(serde_json::Value::Object(legacy))
}
(Some(config), Some(serde_json::Value::Object(mut flattened))) => {
flattened.insert("config".to_string(), config);
Some(serde_json::Value::Object(flattened))
}
(Some(config), Some(other)) => {
return Err(D::Error::custom(format!(
"unexpected plugin config payload combination: config={config}, extra={other}"
)))
}
};
Ok(Self {
id,
enabled,
config,
})
}
}
impl Plugin {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: id.into(),
enabled: None,
config: None,
}
}
pub fn response_healing() -> Self {
Self::new("response-healing")
}
pub fn web_search() -> Self {
Self::new("web")
}
pub fn file_parser() -> Self {
Self::new("file-parser")
}
pub fn context_compression() -> Self {
Self::new("context-compression")
}
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = Some(enabled);
self
}
pub fn with_config(mut self, config: serde_json::Value) -> Self {
self.config = Some(config);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ReasoningEffort {
XHigh,
High,
Medium,
Low,
Minimal,
None,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ReasoningSummary {
Auto,
Concise,
Detailed,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct ReasoningConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<ReasoningEffort>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<ReasoningSummary>,
}
impl ReasoningConfig {
pub fn with_effort(effort: ReasoningEffort) -> Self {
Self {
effort: Some(effort),
..Self::default()
}
}
pub fn with_max_tokens(max_tokens: u32) -> Self {
Self {
max_tokens: Some(max_tokens),
..Self::default()
}
}
pub fn with_summary(mut self, summary: ReasoningSummary) -> Self {
self.summary = Some(summary);
self
}
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = Some(enabled);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "type")]
pub enum ReasoningDetail {
#[serde(rename = "reasoning.summary")]
Summary {
summary: String,
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
index: Option<u32>,
},
#[serde(rename = "reasoning.encrypted")]
Encrypted {
data: String,
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
index: Option<u32>,
},
#[serde(rename = "reasoning.text")]
Text {
#[serde(skip_serializing_if = "Option::is_none")]
text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
index: Option<u32>,
},
}
#[derive(Debug, Serialize, Clone, Default)]
pub struct ChatCompletionRequest {
pub model: String,
pub messages: Vec<Message>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<StreamingStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<crate::api::request::ResponseFormatConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<crate::models::tool::Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<crate::models::provider_preferences::ProviderPreferences>,
#[serde(skip_serializing_if = "Option::is_none")]
pub models: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transforms: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub route: Option<RouteStrategy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_k: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub presence_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub repetition_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_a: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<StopSequence>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logit_bias: Option<HashMap<u32, f32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logprobs: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_logprobs: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prediction: Option<PredictionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parallel_tool_calls: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub verbosity: Option<VerbosityLevel>,
#[serde(skip_serializing_if = "Option::is_none")]
pub debug: Option<DebugConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub plugins: Option<Vec<Plugin>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<ReasoningConfig>,
}
#[derive(Debug, Deserialize)]
pub struct Choice {
pub message: Message,
pub finish_reason: Option<String>,
#[serde(rename = "native_finish_reason")]
pub native_finish_reason: Option<String>,
pub index: Option<u32>,
pub logprobs: Option<LogProbs>,
}
#[derive(Debug, Deserialize)]
pub struct LogProbs {
pub content: Option<Vec<TokenLogProb>>,
}
#[derive(Debug, Deserialize)]
pub struct TokenLogProb {
pub token: String,
pub logprob: f32,
pub bytes: Option<Vec<u8>>,
pub top_logprobs: Option<Vec<TopLogProb>>,
}
#[derive(Debug, Deserialize)]
pub struct TopLogProb {
pub token: String,
pub logprob: f32,
pub bytes: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ServerToolUse {
#[serde(skip_serializing_if = "Option::is_none")]
pub web_search_requests: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub web_fetch_requests: Option<u32>,
}
#[derive(Debug, Deserialize)]
pub struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
pub cost: Option<f64>,
pub is_byok: Option<bool>,
pub server_tool_use: Option<ServerToolUse>,
pub prompt_tokens_details: Option<PromptTokensDetails>,
pub completion_tokens_details: Option<CompletionTokensDetails>,
}
#[derive(Debug, Deserialize)]
pub struct PromptTokensDetails {
pub cached_tokens: Option<u32>,
pub audio_tokens: Option<u32>,
pub text_tokens: Option<u32>,
pub image_tokens: Option<u32>,
pub cache_write_tokens: Option<u32>,
pub video_tokens: Option<u32>,
}
#[derive(Debug, Deserialize)]
pub struct CompletionTokensDetails {
pub reasoning_tokens: Option<u32>,
pub audio_tokens: Option<u32>,
pub accepted_prediction_tokens: Option<u32>,
pub rejected_prediction_tokens: Option<u32>,
}
#[derive(Debug, Deserialize)]
pub struct ChatCompletionResponse {
pub id: String,
pub choices: Vec<Choice>,
pub created: i64,
pub model: String,
pub object: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_fingerprint: Option<String>,
pub usage: Option<Usage>,
}
#[derive(Debug, Deserialize)]
pub struct ChoiceStream {
pub index: u32,
pub delta: StreamDelta,
pub finish_reason: Option<String>,
#[serde(rename = "native_finish_reason")]
pub native_finish_reason: Option<String>,
pub logprobs: Option<LogProbs>,
}
#[derive(Debug, Deserialize)]
pub struct StreamDelta {
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<MessageContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCallChunk>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_details: Option<Vec<ReasoningDetail>>,
}
#[derive(Debug, Deserialize)]
pub struct ChatCompletionChunk {
pub id: String,
pub object: String,
pub created: i64,
pub model: String,
pub choices: Vec<ChoiceStream>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_fingerprint: Option<String>,
}