use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use crate::canonical::error::CanonicalError;
use crate::canonical::request::Role;
pub const EVENT_SCHEMA_VERSION: u8 = 1;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[non_exhaustive]
pub enum Event {
MessageStart {
v: u8,
#[serde(default)]
id: Option<String>,
#[serde(default)]
model: Option<String>,
role: Role,
},
ContentStart {
index: u32,
kind: ContentKind,
},
ContentDelta {
index: u32,
delta: Delta,
},
ContentStop {
index: u32,
},
Usage(Usage),
Finish {
#[serde(flatten)]
reason: FinishReason,
},
Error(CanonicalError),
#[serde(skip)]
Raw(Vec<u8>),
End,
#[serde(other)]
Other,
}
impl Event {
pub fn message_start(id: Option<String>, model: Option<String>, role: Role) -> Event {
Event::MessageStart {
v: EVENT_SCHEMA_VERSION,
id,
model,
role,
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum ContentKind {
Text {},
ToolUse {
id: String,
name: String,
},
Thinking {},
RedactedThinking {},
Other(Value),
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
#[allow(clippy::enum_variant_names)]
pub enum Delta {
TextDelta(String),
JsonDelta(String),
ThinkingDelta(String),
Other(Value),
}
fn tag_of(v: &Value) -> Option<&str> {
v.as_object()?.keys().next().map(String::as_str)
}
fn str_at(v: &Value, key: &str) -> String {
v[key].as_str().unwrap_or_default().to_owned()
}
impl Serialize for ContentKind {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
enum Wire<'a> {
Text {},
ToolUse { id: &'a str, name: &'a str },
Thinking {},
RedactedThinking {},
}
match self {
ContentKind::Text {} => Wire::Text {}.serialize(s),
ContentKind::ToolUse { id, name } => Wire::ToolUse { id, name }.serialize(s),
ContentKind::Thinking {} => Wire::Thinking {}.serialize(s),
ContentKind::RedactedThinking {} => Wire::RedactedThinking {}.serialize(s),
ContentKind::Other(v) => v.serialize(s),
}
}
}
impl<'de> Deserialize<'de> for ContentKind {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let v = Value::deserialize(d)?;
Ok(match tag_of(&v) {
Some("text") => ContentKind::Text {},
Some("tool_use") => ContentKind::ToolUse {
id: str_at(&v["tool_use"], "id"),
name: str_at(&v["tool_use"], "name"),
},
Some("thinking") => ContentKind::Thinking {},
Some("redacted_thinking") => ContentKind::RedactedThinking {},
_ => ContentKind::Other(v),
})
}
}
impl Serialize for Delta {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
#[derive(Serialize)]
enum Wire<'a> {
#[serde(rename = "text_delta")]
Text(&'a str),
#[serde(rename = "json_delta")]
Json(&'a str),
#[serde(rename = "thinking_delta")]
Thinking(&'a str),
}
match self {
Delta::TextDelta(t) => Wire::Text(t).serialize(s),
Delta::JsonDelta(t) => Wire::Json(t).serialize(s),
Delta::ThinkingDelta(t) => Wire::Thinking(t).serialize(s),
Delta::Other(v) => v.serialize(s),
}
}
}
impl<'de> Deserialize<'de> for Delta {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let v = Value::deserialize(d)?;
Ok(match tag_of(&v) {
Some("text_delta") => Delta::TextDelta(str_at(&v, "text_delta")),
Some("json_delta") => Delta::JsonDelta(str_at(&v, "json_delta")),
Some("thinking_delta") => Delta::ThinkingDelta(str_at(&v, "thinking_delta")),
_ => Delta::Other(v),
})
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Usage {
pub input_tokens: Option<u32>,
pub output_tokens: Option<u32>,
pub cache_read_tokens: Option<u32>,
pub cache_write_tokens: Option<u32>,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum FinishReason {
Stop,
Length,
ToolUse,
StopSequence,
Refusal {
category: String,
explanation: Option<String>,
},
Pause,
Other(String),
}
impl Serialize for FinishReason {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
#[derive(Serialize)]
struct Reason<'a> {
reason: &'a str,
}
#[derive(Serialize)]
struct Refusal<'a> {
reason: &'a str,
category: &'a str,
explanation: &'a Option<String>,
}
let reason = match self {
FinishReason::Stop => "stop",
FinishReason::Length => "length",
FinishReason::ToolUse => "tool_use",
FinishReason::StopSequence => "stop_sequence",
FinishReason::Pause => "pause",
FinishReason::Other(reason) => reason.as_str(),
FinishReason::Refusal {
category,
explanation,
} => {
return Refusal {
reason: "refusal",
category,
explanation,
}
.serialize(s)
}
};
Reason { reason }.serialize(s)
}
}
impl<'de> Deserialize<'de> for FinishReason {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
struct Raw {
reason: String,
#[serde(default)]
category: Option<String>,
#[serde(default)]
explanation: Option<String>,
}
let raw = Raw::deserialize(d)?;
Ok(match raw.reason.as_str() {
"stop" => FinishReason::Stop,
"length" => FinishReason::Length,
"tool_use" => FinishReason::ToolUse,
"stop_sequence" => FinishReason::StopSequence,
"pause" => FinishReason::Pause,
"refusal" => FinishReason::Refusal {
category: raw.category.unwrap_or_default(),
explanation: raw.explanation,
},
_ => FinishReason::Other(raw.reason),
})
}
}