use crate::enums::{ContentType, FormatType, OutputType, ReasoningEffort, Role, Status};
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Deserialize)]
pub struct Gpt5Response {
pub id: Option<String>,
pub object: Option<String>,
pub created_at: Option<u64>,
pub status: Option<Status>,
pub error: Option<Value>,
pub incomplete_details: Option<Value>,
pub instructions: Option<String>,
pub max_output_tokens: Option<u32>,
pub model: Option<String>,
pub output: Option<Vec<ResponseOutput>>,
pub parallel_tool_calls: Option<bool>,
pub previous_response_id: Option<String>,
pub reasoning: Option<ResponseReasoning>,
pub store: Option<bool>,
pub text: Option<ResponseText>,
pub tool_choice: Option<String>,
pub tools: Option<Vec<Value>>,
pub top_p: Option<f64>,
pub truncation: Option<String>,
pub usage: Option<ResponseUsage>,
pub user: Option<String>,
pub metadata: Option<HashMap<String, Value>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ResponseOutput {
#[serde(rename = "type")]
pub output_type: OutputType,
pub id: Option<String>,
pub call_id: Option<String>,
pub name: Option<String>,
pub arguments: Option<String>,
pub status: Option<Status>,
pub role: Option<Role>,
pub content: Option<Vec<OutputContent>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OutputContent {
#[serde(rename = "type")]
pub content_type: ContentType,
pub text: Option<String>,
pub annotations: Option<Vec<Value>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ResponseReasoning {
pub effort: Option<ReasoningEffort>,
pub summary: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ResponseText {
pub format: Option<ResponseTextFormat>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ResponseTextFormat {
#[serde(rename = "type")]
pub format_type: FormatType,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ResponseUsage {
pub input_tokens: u32,
pub input_tokens_details: Option<InputTokenDetails>,
pub output_tokens: u32,
pub output_tokens_details: Option<ResponseTokenDetails>,
pub total_tokens: u32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct InputTokenDetails {
pub cached_tokens: Option<u32>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ResponseTokenDetails {
pub reasoning_tokens: Option<u32>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OpenAiError {
pub error: OpenAiErrorDetails,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OpenAiErrorDetails {
pub message: String,
#[serde(rename = "type")]
pub error_type: String,
pub param: Option<String>,
pub code: Option<String>,
}
impl Gpt5Response {
pub fn text(&self) -> Option<String> {
if let Some(outputs) = &self.output {
for output in outputs {
if output.output_type == OutputType::Message {
if let Some(content_array) = &output.content {
for content in content_array {
if content.content_type == ContentType::OutputText {
if let Some(text) = &content.text {
return Some(text.clone());
}
}
}
}
}
}
}
None
}
pub fn all_text(&self) -> Vec<String> {
let mut texts = Vec::new();
if let Some(outputs) = &self.output {
for output in outputs {
if output.output_type == OutputType::Message {
if let Some(content_array) = &output.content {
for content in content_array {
if content.content_type == ContentType::OutputText {
if let Some(text) = &content.text {
texts.push(text.clone());
}
}
}
}
}
}
}
texts
}
pub fn function_calls(&self) -> Vec<&ResponseOutput> {
if let Some(outputs) = &self.output {
outputs
.iter()
.filter(|output| output.output_type == OutputType::FunctionCall)
.collect()
} else {
Vec::new()
}
}
pub fn reasoning_tokens(&self) -> Option<u32> {
self.usage
.as_ref()
.and_then(|usage| usage.output_tokens_details.as_ref())
.and_then(|details| details.reasoning_tokens)
}
pub fn total_tokens(&self) -> u32 {
self.usage.as_ref().map(|u| u.total_tokens).unwrap_or(0)
}
pub fn is_completed(&self) -> bool {
self.status
.as_ref()
.map(|s| *s == Status::Completed)
.unwrap_or(false)
}
pub fn has_error(&self) -> bool {
self.error.is_some()
}
}