use serde::de::{self, Deserializer};
use serde::ser::{SerializeSeq, Serializer};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Usage {
#[serde(default)]
pub prompt_tokens: u32,
#[serde(default)]
pub completion_tokens: u32,
#[serde(default)]
pub total_tokens: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cost_usd: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prompt_tokens_details: Option<PromptTokensDetails>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache_creation_tokens: Option<u32>,
}
impl Usage {
pub fn cached_tokens(&self) -> u32 {
self.prompt_tokens_details
.as_ref()
.and_then(|d| d.cached_tokens)
.unwrap_or(0)
}
pub fn cache_writes(&self) -> u32 {
self.cache_creation_tokens.unwrap_or(0)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PromptTokensDetails {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cached_tokens: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Role {
System,
User,
Assistant,
Tool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FinishReason {
Stop,
Length,
ToolCalls,
ContentFilter,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BatchStatus {
Validating,
InProgress,
Finalizing,
Completed,
Failed,
Expired,
Canceling,
Canceled,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ImageUrl {
pub url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentPart {
Text { text: String },
ImageUrl { image_url: ImageUrl },
}
impl ContentPart {
pub fn text(text: impl Into<String>) -> Self {
ContentPart::Text { text: text.into() }
}
pub fn image_url(url: impl Into<String>) -> Self {
ContentPart::ImageUrl {
image_url: ImageUrl {
url: url.into(),
detail: None,
},
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Content {
Text(String),
Parts(Vec<ContentPart>),
}
impl From<String> for Content {
fn from(s: String) -> Self {
Content::Text(s)
}
}
impl From<&str> for Content {
fn from(s: &str) -> Self {
Content::Text(s.to_string())
}
}
impl From<Vec<ContentPart>> for Content {
fn from(p: Vec<ContentPart>) -> Self {
Content::Parts(p)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionCall {
pub name: String,
pub arguments: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
#[serde(rename = "type")]
pub kind: String,
pub function: FunctionCall,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionCallDelta {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolCallDelta {
pub index: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "type")]
pub kind: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub function: Option<FunctionCallDelta>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReasoningDetail {
#[serde(rename = "type")]
pub kind: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<u32>,
}
impl ReasoningDetail {
pub fn is_hidden(&self) -> bool {
self.kind == "reasoning.encrypted" || (self.data.is_some() && self.text.is_none())
}
pub fn open_text(&self) -> Option<&str> {
self.text.as_deref().or(self.summary.as_deref())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChatMessage {
pub role: Role,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<Content>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_calls: Vec<ToolCall>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reasoning_details: Vec<ReasoningDetail>,
}
impl ChatMessage {
pub fn system(text: impl Into<String>) -> Self {
Self::with_text(Role::System, text)
}
pub fn user(text: impl Into<String>) -> Self {
Self::with_text(Role::User, text)
}
pub fn assistant(text: impl Into<String>) -> Self {
Self::with_text(Role::Assistant, text)
}
fn with_text(role: Role, text: impl Into<String>) -> Self {
ChatMessage {
role,
content: Some(Content::Text(text.into())),
name: None,
tool_calls: Vec::new(),
tool_call_id: None,
reasoning: None,
reasoning_details: Vec::new(),
}
}
pub fn text(&self) -> Option<&str> {
match &self.content {
Some(Content::Text(s)) => Some(s.as_str()),
_ => None,
}
}
pub fn reasoning_text(&self) -> Option<String> {
if let Some(r) = &self.reasoning {
return Some(r.clone());
}
let joined: String = self
.reasoning_details
.iter()
.filter_map(ReasoningDetail::open_text)
.collect();
(!joined.is_empty()).then_some(joined)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionDef {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parameters: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolDef {
#[serde(rename = "type")]
pub kind: String,
pub function: FunctionDef,
}
impl ToolDef {
pub fn function(function: FunctionDef) -> Self {
ToolDef {
kind: "function".to_string(),
function,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionName {
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NamedToolChoice {
#[serde(rename = "type")]
pub kind: String,
pub function: FunctionName,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ToolChoice {
Mode(String),
Named(NamedToolChoice),
}
impl ToolChoice {
pub fn mode(mode: impl Into<String>) -> Self {
ToolChoice::Mode(mode.into())
}
pub fn named(name: impl Into<String>) -> Self {
ToolChoice::Named(NamedToolChoice {
kind: "function".to_string(),
function: FunctionName { name: name.into() },
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponseFormat {
#[serde(rename = "type")]
pub kind: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub json_schema: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Stop {
One(String),
Many(Vec<String>),
}
impl Stop {
pub fn from_vec(mut v: Vec<String>) -> Option<Self> {
match v.len() {
0 => None,
1 => Some(Stop::One(v.pop().unwrap())),
_ => Some(Stop::Many(v)),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct ChatRequest {
pub model: String,
pub messages: Vec<ChatMessage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_completion_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stop: Option<Stop>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub n: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub presence_penalty: Option<f32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<ToolDef>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_format: Option<ResponseFormat>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning_effort: Option<String>,
#[serde(default, flatten, skip_serializing_if = "Option::is_none")]
pub extra: Option<serde_json::Map<String, serde_json::Value>>,
}
impl ChatRequest {
pub fn new(model: impl Into<String>, messages: Vec<ChatMessage>) -> Self {
ChatRequest {
model: model.into(),
messages,
..Default::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Choice {
#[serde(default)]
pub index: u32,
pub message: ChatMessage,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub finish_reason: Option<FinishReason>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChatResponse {
pub id: String,
pub object: String,
pub created: i64,
pub model: String,
pub choices: Vec<Choice>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
}
impl ChatResponse {
pub fn first_text(&self) -> Option<&str> {
self.choices.first().and_then(|c| c.message.text())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Delta {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub role: Option<Role>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_calls: Vec<ToolCallDelta>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reasoning_details: Vec<ReasoningDetail>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChunkChoice {
#[serde(default)]
pub index: u32,
pub delta: Delta,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub finish_reason: Option<FinishReason>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChatCompletionChunk {
pub id: String,
pub object: String,
pub created: i64,
pub model: String,
pub choices: Vec<ChunkChoice>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
}
impl ChatCompletionChunk {
pub fn first_delta_text(&self) -> Option<&str> {
self.choices
.first()
.and_then(|c| c.delta.content.as_deref())
}
pub fn first_delta_reasoning(&self) -> Option<&str> {
self.choices
.first()
.and_then(|c| c.delta.reasoning.as_deref())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponseFunctionCallOutputItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub call_id: String,
pub output: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponseFunctionCallItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub call_id: String,
pub name: String,
pub arguments: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponseReasoningText {
pub text: String,
}
impl ResponseReasoningText {
pub fn new(text: impl Into<String>) -> Self {
ResponseReasoningText { text: text.into() }
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct ResponseReasoningItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_summary_texts"
)]
pub summary: Vec<ResponseReasoningText>,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_content_texts"
)]
pub content: Vec<ResponseReasoningText>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encrypted_content: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseContentPart {
InputText {
text: String,
},
InputImage {
image_url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
detail: Option<String>,
},
OutputText {
text: String,
#[serde(default)]
annotations: Vec<serde_json::Value>,
},
}
impl ResponseContentPart {
pub fn input_text(text: impl Into<String>) -> Self {
ResponseContentPart::InputText { text: text.into() }
}
pub fn input_image(image_url: impl Into<String>) -> Self {
ResponseContentPart::InputImage {
image_url: image_url.into(),
detail: None,
}
}
pub fn output_text(text: impl Into<String>) -> Self {
ResponseContentPart::OutputText {
text: text.into(),
annotations: Vec::new(),
}
}
pub fn text(&self) -> Option<&str> {
match self {
ResponseContentPart::InputText { text }
| ResponseContentPart::OutputText { text, .. } => Some(text.as_str()),
ResponseContentPart::InputImage { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ResponseContent {
Text(String),
Parts(Vec<ResponseContentPart>),
}
impl From<String> for ResponseContent {
fn from(s: String) -> Self {
ResponseContent::Text(s)
}
}
impl From<&str> for ResponseContent {
fn from(s: &str) -> Self {
ResponseContent::Text(s.to_string())
}
}
impl From<Vec<ResponseContentPart>> for ResponseContent {
fn from(p: Vec<ResponseContentPart>) -> Self {
ResponseContent::Parts(p)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponseMessageItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub role: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<ResponseContent>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
}
impl ResponseMessageItem {
fn with_role(role: &str, content: impl Into<ResponseContent>) -> Self {
ResponseMessageItem {
id: None,
role: role.to_string(),
content: Some(content.into()),
status: None,
}
}
pub fn user(content: impl Into<ResponseContent>) -> Self {
Self::with_role("user", content)
}
pub fn system(content: impl Into<ResponseContent>) -> Self {
Self::with_role("system", content)
}
pub fn developer(content: impl Into<ResponseContent>) -> Self {
Self::with_role("developer", content)
}
pub fn assistant(content: impl Into<ResponseContent>) -> Self {
Self::with_role("assistant", content)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResponseItem {
Message(ResponseMessageItem),
FunctionCall(ResponseFunctionCallItem),
FunctionCallOutput(ResponseFunctionCallOutputItem),
Reasoning(ResponseReasoningItem),
Other(serde_json::Value),
}
impl ResponseItem {
pub fn user(content: impl Into<ResponseContent>) -> Self {
ResponseItem::Message(ResponseMessageItem::user(content))
}
pub fn system(content: impl Into<ResponseContent>) -> Self {
ResponseItem::Message(ResponseMessageItem::system(content))
}
pub fn developer(content: impl Into<ResponseContent>) -> Self {
ResponseItem::Message(ResponseMessageItem::developer(content))
}
pub fn assistant(content: impl Into<ResponseContent>) -> Self {
ResponseItem::Message(ResponseMessageItem::assistant(content))
}
pub fn function_call(
call_id: impl Into<String>,
name: impl Into<String>,
arguments: impl Into<String>,
) -> Self {
ResponseItem::FunctionCall(ResponseFunctionCallItem {
id: None,
call_id: call_id.into(),
name: name.into(),
arguments: arguments.into(),
status: None,
})
}
pub fn function_call_output(call_id: impl Into<String>, output: impl Into<String>) -> Self {
ResponseItem::FunctionCallOutput(ResponseFunctionCallOutputItem {
id: None,
call_id: call_id.into(),
output: output.into(),
})
}
}
#[derive(Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum TypedItemRef<'a> {
FunctionCall(&'a ResponseFunctionCallItem),
FunctionCallOutput(&'a ResponseFunctionCallOutputItem),
Reasoning(&'a ResponseReasoningItem),
}
impl Serialize for ResponseItem {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
ResponseItem::Message(m) => m.serialize(serializer),
ResponseItem::FunctionCall(f) => TypedItemRef::FunctionCall(f).serialize(serializer),
ResponseItem::FunctionCallOutput(f) => {
TypedItemRef::FunctionCallOutput(f).serialize(serializer)
}
ResponseItem::Reasoning(r) => TypedItemRef::Reasoning(r).serialize(serializer),
ResponseItem::Other(v) => v.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for ResponseItem {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
let ty = value
.get("type")
.and_then(serde_json::Value::as_str)
.map(str::to_owned);
let item = match ty.as_deref() {
None | Some("message") => {
ResponseItem::Message(serde_json::from_value(value).map_err(de::Error::custom)?)
}
Some("function_call") => {
ResponseItem::FunctionCall(serde_json::from_value(value).map_err(de::Error::custom)?)
}
Some("function_call_output") => ResponseItem::FunctionCallOutput(
serde_json::from_value(value).map_err(de::Error::custom)?,
),
Some("reasoning") => {
ResponseItem::Reasoning(serde_json::from_value(value).map_err(de::Error::custom)?)
}
Some(_) => ResponseItem::Other(value),
};
Ok(item)
}
}
#[derive(Serialize)]
struct TaggedReasoningText<'a> {
#[serde(rename = "type")]
kind: &'static str,
text: &'a str,
}
fn serialize_reasoning_texts<S: Serializer>(
texts: &[ResponseReasoningText],
kind: &'static str,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut seq = serializer.serialize_seq(Some(texts.len()))?;
for t in texts {
seq.serialize_element(&TaggedReasoningText {
kind,
text: &t.text,
})?;
}
seq.end()
}
fn serialize_summary_texts<S: Serializer>(
texts: &[ResponseReasoningText],
serializer: S,
) -> Result<S::Ok, S::Error> {
serialize_reasoning_texts(texts, "summary_text", serializer)
}
fn serialize_content_texts<S: Serializer>(
texts: &[ResponseReasoningText],
serializer: S,
) -> Result<S::Ok, S::Error> {
serialize_reasoning_texts(texts, "reasoning_text", serializer)
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponsesToolDef {
#[serde(rename = "type")]
pub kind: String,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parameters: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strict: Option<bool>,
}
impl ResponsesToolDef {
pub fn function(name: impl Into<String>) -> Self {
ResponsesToolDef {
kind: "function".to_string(),
name: name.into(),
description: None,
parameters: None,
strict: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponsesNamedToolChoice {
#[serde(rename = "type")]
pub kind: String,
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ResponsesToolChoice {
Mode(String),
Named(ResponsesNamedToolChoice),
}
impl ResponsesToolChoice {
pub fn mode(mode: impl Into<String>) -> Self {
ResponsesToolChoice::Mode(mode.into())
}
pub fn named(name: impl Into<String>) -> Self {
ResponsesToolChoice::Named(ResponsesNamedToolChoice {
kind: "function".to_string(),
name: name.into(),
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct ResponsesReasoning {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub effort: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ResponsesInput {
Text(String),
Items(Vec<ResponseItem>),
}
impl From<String> for ResponsesInput {
fn from(s: String) -> Self {
ResponsesInput::Text(s)
}
}
impl From<&str> for ResponsesInput {
fn from(s: &str) -> Self {
ResponsesInput::Text(s.to_string())
}
}
impl From<Vec<ResponseItem>> for ResponsesInput {
fn from(items: Vec<ResponseItem>) -> Self {
ResponsesInput::Items(items)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponsesRequest {
pub model: String,
pub input: ResponsesInput,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<ResponsesToolDef>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ResponsesToolChoice>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning: Option<ResponsesReasoning>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub store: Option<bool>,
#[serde(default, flatten, skip_serializing_if = "Option::is_none")]
pub extra: Option<serde_json::Map<String, serde_json::Value>>,
}
impl ResponsesRequest {
pub fn new(model: impl Into<String>, input: impl Into<ResponsesInput>) -> Self {
ResponsesRequest {
model: model.into(),
input: input.into(),
instructions: None,
stream: None,
temperature: None,
top_p: None,
max_output_tokens: None,
tools: Vec::new(),
tool_choice: None,
reasoning: None,
store: None,
extra: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponsesInputTokensDetails {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cached_tokens: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponsesOutputTokensDetails {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning_tokens: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponsesUsage {
#[serde(default)]
pub input_tokens: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input_tokens_details: Option<ResponsesInputTokensDetails>,
#[serde(default)]
pub output_tokens: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_tokens_details: Option<ResponsesOutputTokensDetails>,
#[serde(default)]
pub total_tokens: u32,
}
impl ResponsesUsage {
pub fn cached_tokens(&self) -> u32 {
self.input_tokens_details
.as_ref()
.and_then(|d| d.cached_tokens)
.unwrap_or(0)
}
pub fn reasoning_tokens(&self) -> u32 {
self.output_tokens_details
.as_ref()
.and_then(|d| d.reasoning_tokens)
.unwrap_or(0)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponsesIncompleteDetails {
pub reason: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponsesError {
pub message: String,
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponsesResponse {
pub id: String,
pub object: String,
#[serde(default)]
pub created_at: i64,
pub status: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub incomplete_details: Option<ResponsesIncompleteDetails>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<ResponsesError>,
#[serde(default)]
pub model: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub output: Vec<ResponseItem>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<ResponsesUsage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub store: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning: Option<ResponsesReasoning>,
}
impl ResponsesResponse {
pub fn output_text(&self) -> String {
let mut out = String::new();
for item in &self.output {
let ResponseItem::Message(msg) = item else {
continue;
};
match &msg.content {
Some(ResponseContent::Text(t)) => out.push_str(t),
Some(ResponseContent::Parts(parts)) => {
for part in parts {
if let ResponseContentPart::OutputText { text, .. } = part {
out.push_str(text);
}
}
}
None => {}
}
}
out
}
pub fn function_calls(&self) -> Vec<&ResponseFunctionCallItem> {
self.output
.iter()
.filter_map(|item| match item {
ResponseItem::FunctionCall(f) => Some(f),
_ => None,
})
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponsesStreamEvent {
#[serde(rename = "type")]
pub kind: String,
#[serde(default)]
pub sequence_number: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response: Option<ResponsesResponse>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_index: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub item_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_index: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub item: Option<ResponseItem>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub part: Option<ResponseContentPart>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub delta: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
impl ResponsesStreamEvent {
pub fn is_terminal(&self) -> bool {
matches!(
self.kind.as_str(),
"response.completed" | "response.incomplete" | "response.failed"
)
}
pub fn is_error(&self) -> bool {
self.kind == "error"
}
pub fn output_text_delta(&self) -> Option<&str> {
(self.kind == "response.output_text.delta")
.then_some(self.delta.as_deref())
.flatten()
}
pub fn terminal_response(&self) -> Option<&ResponsesResponse> {
self.is_terminal().then_some(self.response.as_ref()).flatten()
}
pub(crate) fn is_recognised(&self) -> bool {
self.kind == "error" || self.kind.starts_with("response.")
}
pub(crate) fn error_message(&self) -> String {
self.message
.clone()
.or_else(|| {
self.response
.as_ref()
.and_then(|r| r.error.as_ref().map(|e| e.message.clone()))
})
.unwrap_or_default()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum EmbeddingInput {
One(String),
Many(Vec<String>),
}
impl From<String> for EmbeddingInput {
fn from(s: String) -> Self {
EmbeddingInput::One(s)
}
}
impl From<&str> for EmbeddingInput {
fn from(s: &str) -> Self {
EmbeddingInput::One(s.to_string())
}
}
impl From<Vec<String>> for EmbeddingInput {
fn from(v: Vec<String>) -> Self {
EmbeddingInput::Many(v)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EmbeddingRequest {
pub model: String,
pub input: EmbeddingInput,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dimensions: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encoding_format: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extra: Option<serde_json::Value>,
}
impl EmbeddingRequest {
pub fn new(model: impl Into<String>, input: impl Into<EmbeddingInput>) -> Self {
EmbeddingRequest {
model: model.into(),
input: input.into(),
dimensions: None,
encoding_format: None,
extra: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Embedding {
pub object: String,
#[serde(default)]
pub index: u32,
pub embedding: Vec<f32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EmbeddingResponse {
pub object: String,
pub data: Vec<Embedding>,
pub model: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RerankDocument {
Text(String),
Object(serde_json::Value),
}
impl From<String> for RerankDocument {
fn from(s: String) -> Self {
RerankDocument::Text(s)
}
}
impl From<&str> for RerankDocument {
fn from(s: &str) -> Self {
RerankDocument::Text(s.to_string())
}
}
impl From<serde_json::Value> for RerankDocument {
fn from(v: serde_json::Value) -> Self {
RerankDocument::Object(v)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RerankRequest {
pub model: String,
pub query: String,
pub documents: Vec<RerankDocument>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub top_n: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub return_documents: Option<bool>,
}
impl RerankRequest {
pub fn new(
model: impl Into<String>,
query: impl Into<String>,
documents: impl IntoIterator<Item = impl Into<RerankDocument>>,
) -> Self {
RerankRequest {
model: model.into(),
query: query.into(),
documents: documents.into_iter().map(Into::into).collect(),
top_n: None,
return_documents: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RerankResult {
#[serde(default)]
pub index: u32,
pub relevance_score: f32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub document: Option<RerankDocument>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RerankResponse {
pub model: String,
pub results: Vec<RerankResult>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SpeechRequest {
pub model: String,
pub input: String,
pub voice: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_format: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub speed: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extra: Option<serde_json::Value>,
}
impl SpeechRequest {
pub fn new(
model: impl Into<String>,
input: impl Into<String>,
voice: impl Into<String>,
) -> Self {
SpeechRequest {
model: model.into(),
input: input.into(),
voice: voice.into(),
response_format: None,
speed: None,
extra: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Voice {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub languages: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VoicesResponse {
pub model: String,
pub voices: Vec<Voice>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct TranscriptionRequest {
pub model: String,
pub language: Option<String>,
pub prompt: Option<String>,
pub response_format: Option<String>,
pub temperature: Option<f32>,
}
impl TranscriptionRequest {
pub fn new(model: impl Into<String>) -> Self {
TranscriptionRequest {
model: model.into(),
..Default::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TranscriptionResponse {
pub text: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub task: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub duration: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Transcription {
Json(TranscriptionResponse),
Text(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Architecture {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub input_modalities: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub output_modalities: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub modality: Option<String>,
#[serde(default)]
pub tokenizer: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instruct_type: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Pricing {
pub prompt: String,
pub completion: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TopProvider {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_length: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_completion_tokens: Option<u32>,
#[serde(default)]
pub is_moderated: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_thinking_tokens: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ModelEndpoint {
pub provider: String,
pub model: String,
#[serde(default)]
pub down: bool,
pub source: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ModelEntry {
pub id: String,
#[serde(default)]
pub canonical_slug: String,
#[serde(default)]
pub name: String,
#[serde(default)]
pub created: i64,
#[serde(default)]
pub description: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_length: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub architecture: Option<Architecture>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pricing: Option<Pricing>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub top_provider: Option<TopProvider>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub supported_parameters: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub unsupported_parameters: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_parameters: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub endpoints: Vec<ModelEndpoint>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListModelsResponse {
#[serde(default)]
pub data: Vec<ModelEntry>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModelType {
All,
Llm,
Tts,
Stt,
Embedding,
Rerank,
}
impl ModelType {
pub(crate) fn as_str(self) -> &'static str {
match self {
ModelType::All => "all",
ModelType::Llm => "llm",
ModelType::Tts => "tts",
ModelType::Stt => "stt",
ModelType::Embedding => "embedding",
ModelType::Rerank => "rerank",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BatchRequestItem {
pub custom_id: String,
pub body: ChatRequest,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BatchCreateRequest {
pub requests: Vec<BatchRequestItem>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct BatchCounts {
#[serde(default)]
pub total: u64,
#[serde(default)]
pub processing: u64,
#[serde(default)]
pub succeeded: u64,
#[serde(default)]
pub errored: u64,
#[serde(default)]
pub canceled: u64,
#[serde(default)]
pub expired: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BatchHandle {
pub id: String,
pub status: BatchStatus,
#[serde(default)]
pub counts: BatchCounts,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub created_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ended_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub endpoint: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BatchResponse {
pub status_code: u32,
pub body: ChatResponse,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BatchError {
pub code: String,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BatchResultLine {
pub custom_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response: Option<BatchResponse>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<BatchError>,
}