use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::create::{Reasoning, ResponseTextConfig};
use super::output::{FunctionCall, ResponseOutputItem};
use super::tools::{ResponseTool, ResponseToolChoice};
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
pub struct ResponseError {
pub code: String,
pub message: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
pub struct IncompleteDetails {
#[serde(default)]
pub reason: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
pub struct ResponseAnnotation {
#[serde(rename = "type")]
pub type_: String,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub start_index: Option<i64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub end_index: Option<i64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub file_id: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
pub struct InputTokensDetails {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub cached_tokens: Option<i64>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
pub struct OutputTokensDetails {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_tokens: Option<i64>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
pub struct ResponseUsage {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub input_tokens: Option<i64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub output_tokens: Option<i64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub total_tokens: Option<i64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub input_tokens_details: Option<InputTokensDetails>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub output_tokens_details: Option<OutputTokensDetails>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
pub struct Response {
pub id: String,
pub object: String,
pub created_at: f64,
pub model: String,
pub output: Vec<ResponseOutputItem>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ResponseError>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub incomplete_details: Option<IncompleteDetails>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, String>>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<i64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_response_id: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<ResponseUsage>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ResponseTool>>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ResponseToolChoice>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub parallel_tool_calls: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub truncation: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<Reasoning>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub service_tier: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<ResponseTextConfig>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub completed_at: Option<f64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub background: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub user: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub top_logprobs: Option<i64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tool_calls: Option<i64>,
}
impl Response {
pub fn output_text(&self) -> String {
let mut result = String::new();
for item in &self.output {
if let Some(content) = &item.content {
for block in content {
if block.type_ == "output_text"
&& let Some(text) = &block.text
{
result.push_str(text);
}
}
}
}
result
}
pub fn function_calls(&self) -> Vec<FunctionCall> {
self.output
.iter()
.filter(|item| item.type_ == "function_call")
.map(|item| {
let call_id = item
.call_id
.as_deref()
.or(item.id.as_deref())
.unwrap_or("unknown")
.to_string();
let name = item.name.clone().unwrap_or_default();
let arguments = item
.arguments
.as_deref()
.and_then(|s| serde_json::from_str(s).ok())
.unwrap_or(serde_json::Value::Object(Default::default()));
FunctionCall {
call_id,
name,
arguments,
}
})
.collect()
}
pub fn has_function_calls(&self) -> bool {
self.output.iter().any(|item| item.type_ == "function_call")
}
}
pub type InputTokenDetails = InputTokensDetails;
pub type OutputTokenDetails = OutputTokensDetails;