use super::cache::CacheWarning;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerateResponse {
pub content: Vec<ResponseContent>,
pub usage: Usage,
pub finish_reason: FinishReason,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub warnings: Option<Vec<ResponseWarning>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseWarning {
pub warning_type: String,
pub message: String,
}
impl ResponseWarning {
pub fn new(warning_type: impl Into<String>, message: impl Into<String>) -> Self {
Self {
warning_type: warning_type.into(),
message: message.into(),
}
}
}
impl From<CacheWarning> for ResponseWarning {
fn from(warning: CacheWarning) -> Self {
Self {
warning_type: warning.warning_type.to_string(),
message: warning.message,
}
}
}
impl GenerateResponse {
pub fn text(&self) -> String {
self.content
.iter()
.filter_map(|c| match c {
ResponseContent::Text { text } => Some(text.clone()),
_ => None,
})
.collect::<Vec<_>>()
.join("")
}
pub fn reasoning(&self) -> Option<String> {
let reasoning: Vec<String> = self
.content
.iter()
.filter_map(|c| match c {
ResponseContent::Reasoning { reasoning } => Some(reasoning.clone()),
_ => None,
})
.collect();
if reasoning.is_empty() {
None
} else {
Some(reasoning.join(""))
}
}
pub fn tool_calls(&self) -> Vec<&ToolCall> {
self.content
.iter()
.filter_map(|c| match c {
ResponseContent::ToolCall(call) => Some(call),
_ => None,
})
.collect()
}
pub fn has_warnings(&self) -> bool {
self.warnings
.as_ref()
.map(|w| !w.is_empty())
.unwrap_or(false)
}
pub fn warnings(&self) -> &[ResponseWarning] {
self.warnings.as_deref().unwrap_or(&[])
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseContent {
Text {
text: String,
},
Reasoning {
reasoning: String,
},
ToolCall(ToolCall),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct InputTokenDetails {
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub no_cache: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_read: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_write: Option<u32>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct OutputTokenDetails {
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<u32>,
}
#[derive(Debug, Clone, Default, 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 input_token_details: Option<InputTokenDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_token_details: Option<OutputTokenDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub raw: Option<Value>,
}
impl Usage {
pub fn new(prompt_tokens: u32, completion_tokens: u32) -> Self {
Self {
prompt_tokens,
completion_tokens,
total_tokens: prompt_tokens + completion_tokens,
input_token_details: None,
output_token_details: None,
raw: None,
}
}
pub fn with_details(
input_tokens: InputTokenDetails,
output_tokens: OutputTokenDetails,
raw: Option<Value>,
) -> Self {
let prompt_tokens = input_tokens.total.unwrap_or(0);
let completion_tokens = output_tokens.total.unwrap_or(0);
Self {
prompt_tokens,
completion_tokens,
total_tokens: prompt_tokens + completion_tokens,
input_token_details: Some(input_tokens),
output_token_details: Some(output_tokens),
raw,
}
}
pub fn cache_read_tokens(&self) -> Option<u32> {
self.input_token_details.as_ref().and_then(|d| d.cache_read)
}
pub fn cache_write_tokens(&self) -> Option<u32> {
self.input_token_details
.as_ref()
.and_then(|d| d.cache_write)
}
pub fn reasoning_tokens(&self) -> Option<u32> {
self.output_token_details.as_ref().and_then(|d| d.reasoning)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FinishReasonKind {
Stop,
Length,
ContentFilter,
ToolCalls,
Error,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FinishReason {
pub unified: FinishReasonKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub raw: Option<String>,
}
impl FinishReason {
pub fn new(unified: FinishReasonKind) -> Self {
Self { unified, raw: None }
}
pub fn with_raw(unified: FinishReasonKind, raw: impl Into<String>) -> Self {
Self {
unified,
raw: Some(raw.into()),
}
}
pub fn stop() -> Self {
Self::new(FinishReasonKind::Stop)
}
pub fn length() -> Self {
Self::new(FinishReasonKind::Length)
}
pub fn content_filter() -> Self {
Self::new(FinishReasonKind::ContentFilter)
}
pub fn tool_calls() -> Self {
Self::new(FinishReasonKind::ToolCalls)
}
pub fn error() -> Self {
Self::new(FinishReasonKind::Error)
}
pub fn other() -> Self {
Self::new(FinishReasonKind::Other)
}
}
impl Default for FinishReason {
fn default() -> Self {
Self::new(FinishReasonKind::Other)
}
}
impl PartialEq<FinishReasonKind> for FinishReason {
fn eq(&self, other: &FinishReasonKind) -> bool {
self.unified == *other
}
}