use core::fmt;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::{Map, Number, Value};
use super::{Extra, Field};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResultMessage {
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub subtype: Field<ResultSubtype>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub is_error: Field<bool>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub result: Field<Value>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub duration_ms: Field<u64>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub duration_api_ms: Field<u64>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub num_turns: Field<u64>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub total_cost_usd: Field<Number>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub usage: Field<Usage>,
#[serde(
default,
rename = "modelUsage",
skip_serializing_if = "Field::is_absent"
)]
pub model_usage: Field<Map<String, Value>>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub stop_reason: Field<String>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub errors: Field<Value>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub session_id: Field<String>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub uuid: Field<String>,
#[serde(flatten)]
pub extra: Extra,
}
impl ResultMessage {
pub fn cost_usd(&self) -> Option<f64> {
self.total_cost_usd.get().and_then(Number::as_f64)
}
pub fn is_success(&self) -> bool {
self.subtype.get().is_some_and(ResultSubtype::is_success) && !self.failed()
}
pub fn failed(&self) -> bool {
self.is_error.get().copied().unwrap_or(false)
|| self.subtype.get().is_some_and(ResultSubtype::is_error)
}
pub fn result_text(&self) -> Option<&str> {
self.result.get().and_then(Value::as_str)
}
pub fn error_messages(&self) -> Vec<&str> {
match self.errors.get() {
Some(Value::String(s)) => vec![s.as_str()],
Some(Value::Array(a)) => a.iter().filter_map(Value::as_str).collect(),
_ => Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ResultSubtype {
Success,
ErrorMaxTurns,
ErrorDuringExecution,
ErrorMaxBudgetUsd,
ErrorMaxStructuredOutputRetries,
Other(String),
NotAString(Value),
}
impl ResultSubtype {
pub fn as_str(&self) -> &str {
match self {
Self::Success => "success",
Self::ErrorMaxTurns => "error_max_turns",
Self::ErrorDuringExecution => "error_during_execution",
Self::ErrorMaxBudgetUsd => "error_max_budget_usd",
Self::ErrorMaxStructuredOutputRetries => "error_max_structured_output_retries",
Self::Other(s) => s,
Self::NotAString(_) => "",
}
}
pub const fn is_success(&self) -> bool {
matches!(self, Self::Success)
}
pub fn is_error(&self) -> bool {
self.as_str().starts_with("error")
}
}
impl From<&str> for ResultSubtype {
fn from(s: &str) -> Self {
match s {
"success" => Self::Success,
"error_max_turns" => Self::ErrorMaxTurns,
"error_during_execution" => Self::ErrorDuringExecution,
"error_max_budget_usd" => Self::ErrorMaxBudgetUsd,
"error_max_structured_output_retries" => Self::ErrorMaxStructuredOutputRetries,
s => Self::Other(s.to_owned()),
}
}
}
impl fmt::Display for ResultSubtype {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl Serialize for ResultSubtype {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
match self {
Self::NotAString(v) => v.serialize(s),
_ => s.serialize_str(self.as_str()),
}
}
}
impl<'de> Deserialize<'de> for ResultSubtype {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
Ok(match Value::deserialize(d)? {
Value::String(s) => Self::from(s.as_str()),
v => Self::NotAString(v),
})
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Usage {
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub input_tokens: Field<u64>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub output_tokens: Field<u64>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub cache_creation_input_tokens: Field<u64>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub cache_read_input_tokens: Field<u64>,
#[serde(default, skip_serializing_if = "Field::is_absent")]
pub service_tier: Field<String>,
#[serde(flatten)]
pub extra: Extra,
}
impl Usage {
pub fn context_tokens(&self) -> u64 {
[
&self.input_tokens,
&self.cache_read_input_tokens,
&self.cache_creation_input_tokens,
]
.into_iter()
.filter_map(Field::get)
.sum()
}
}