use serde::{Deserialize, Serialize};
use super::config::ProviderPreferences;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum ReasoningFormat {
Unknown,
#[serde(rename = "openai-responses-v1")]
OpenaiResponsesV1,
#[serde(rename = "xai-responses-v1")]
XaiResponsesV1,
#[serde(rename = "anthropic-claude-v1")]
#[default]
AnthropicClaudeV1,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ReasoningDetail {
#[serde(rename = "reasoning.summary")]
Summary {
summary: String,
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default)]
format: ReasoningFormat,
#[serde(skip_serializing_if = "Option::is_none")]
index: Option<usize>,
},
#[serde(rename = "reasoning.encrypted")]
Encrypted {
data: String,
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default)]
format: ReasoningFormat,
#[serde(skip_serializing_if = "Option::is_none")]
index: Option<usize>,
},
#[serde(rename = "reasoning.text")]
Text {
text: String,
#[serde(skip_serializing_if = "Option::is_none")]
signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default)]
format: ReasoningFormat,
#[serde(skip_serializing_if = "Option::is_none")]
index: Option<usize>,
},
}
impl ReasoningDetail {
pub fn extract_text(&self) -> &str {
match self {
Self::Summary { summary, .. } => summary,
Self::Encrypted { .. } => "[REDACTED]",
Self::Text { text, .. } => text,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolSpec {
#[serde(rename = "type")]
pub type_field: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub parameters: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub strict: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatCompletionsTool {
#[serde(rename = "type")]
pub type_field: String,
pub function: ChatCompletionsToolFunction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatCompletionsToolFunction {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub parameters: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub strict: Option<bool>,
}
impl From<ToolSpec> for ChatCompletionsTool {
fn from(spec: ToolSpec) -> Self {
Self {
type_field: spec.type_field,
function: ChatCompletionsToolFunction {
name: spec.name,
description: spec.description,
parameters: spec.parameters,
strict: spec.strict,
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
#[serde(rename = "type")]
pub type_field: String,
pub function: ToolCallFunction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallFunction {
pub name: String,
pub arguments: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ToolChoice {
String(String),
Specific {
#[serde(rename = "type")]
type_field: String,
function: ToolChoiceFunction,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolChoiceFunction {
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorResponse {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_tokens_details: Option<PromptTokensDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completion_tokens_details: Option<CompletionTokensDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost_details: Option<CostDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cached_tokens: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptTokensDetails {
#[serde(default)]
pub cached_tokens: u32,
#[serde(default)]
pub audio_tokens: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompletionTokensDetails {
#[serde(default)]
pub reasoning_tokens: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostDetails {
#[serde(skip_serializing_if = "Option::is_none")]
pub upstream_inference_cost: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub upstream_inference_input_cost: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub upstream_inference_output_cost: Option<f64>,
}
impl Usage {
pub fn to_unified(&self) -> crate::llm::unified::UnifiedUsage {
crate::llm::unified::UnifiedUsage {
input_tokens: self.prompt_tokens,
output_tokens: self.completion_tokens,
cache_read_input_tokens: self.prompt_tokens_details.as_ref().map(|d| d.cached_tokens),
cache_creation_input_tokens: None, reasoning_tokens: self
.completion_tokens_details
.as_ref()
.map(|d| d.reasoning_tokens),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CompletionRequest {
pub model: String,
pub messages: Vec<CompletionMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ChatCompletionsTool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parallel_tool_calls: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[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<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<ProviderPreferences>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transforms: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub models: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub route: Option<String>,
pub stream: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<serde_json::Value>,
}
impl Default for CompletionRequest {
fn default() -> Self {
Self {
model: "openai/gpt-5".to_string(),
messages: Vec::new(),
tools: None,
tool_choice: None,
parallel_tool_calls: Some(false),
temperature: None,
max_tokens: None,
top_p: None,
top_k: None,
frequency_penalty: None,
presence_penalty: None,
repetition_penalty: None,
min_p: None,
top_a: None,
seed: None,
stop: None,
reasoning: None,
provider: None,
transforms: None,
models: None,
route: None,
stream: true,
usage: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompletionMessage {
pub role: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCall>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
#[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, Clone, Deserialize)]
pub struct CompletionChunk {
pub id: String,
pub choices: Vec<CompletionChoice>,
pub created: i64,
pub model: String,
pub object: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CompletionChoice {
pub index: usize,
pub delta: CompletionDelta,
#[serde(skip_serializing_if = "Option::is_none")]
pub finish_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub native_finish_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ErrorResponse>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CompletionDelta {
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCallDelta>>,
#[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, Clone, Deserialize)]
pub struct ToolCallDelta {
pub index: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_field: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub function: Option<FunctionCallDelta>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FunctionCallDelta {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ToolCallBuilder {
pub id: String,
pub name: String,
pub arguments: String,
}
impl ToolCallBuilder {
pub fn new(id: String) -> Self {
Self {
id,
name: String::new(),
arguments: String::new(),
}
}
pub fn finalize(self) -> ToolCall {
ToolCall {
id: self.id,
type_field: "function".to_string(),
function: ToolCallFunction {
name: self.name,
arguments: self.arguments,
},
}
}
}