use std::borrow::Cow;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Api(Cow<'static, str>);
impl Api {
pub const fn from_static(name: &'static str) -> Self {
Self(Cow::Borrowed(name))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&'static str> for Api {
fn from(name: &'static str) -> Self {
Self::from_static(name)
}
}
impl From<String> for Api {
fn from(name: String) -> Self {
Self(Cow::Owned(name))
}
}
impl std::fmt::Display for Api {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Origin {
pub api: Api,
pub provider: String,
pub model: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context: Option<Fingerprint>,
}
impl Origin {
pub fn new(api: impl Into<Api>, provider: impl Into<String>, model: impl Into<String>) -> Self {
Self {
api: api.into(),
provider: provider.into(),
model: model.into(),
response_model: None,
response_id: None,
context: None,
}
}
pub fn same_model(&self, api: &Api, provider: &str, model: &str) -> bool {
&self.api == api && self.provider == provider && self.model == model
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum StopReason {
Stop,
Length,
ToolUse,
Error(String),
Aborted(String),
}
impl StopReason {
pub fn is_failure(&self) -> bool {
matches!(self, Self::Error(_) | Self::Aborted(_))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Fingerprint(u64);
impl Serialize for Fingerprint {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&format!("{:016x}", self.0))
}
}
impl<'de> Deserialize<'de> for Fingerprint {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let text = std::borrow::Cow::<'de, str>::deserialize(deserializer)?;
u64::from_str_radix(&text, 16)
.map(Self)
.map_err(|_| serde::de::Error::custom("a fingerprint is 16 hex digits"))
}
}
impl Fingerprint {
pub fn of(value: &impl Serialize) -> Self {
let mut hash = Fnv(0xcbf2_9ce4_8422_2325);
if let Ok(value) = serde_json::to_value(value) {
let _ = serde_json::to_writer(&mut hash, &Canonical(&value));
}
Self(hash.0)
}
pub(crate) fn of_canonical(value: &impl Serialize) -> Self {
let mut hash = Fnv(0xcbf2_9ce4_8422_2325);
let _ = serde_json::to_writer(&mut hash, value);
Self(hash.0)
}
}
struct Fnv(u64);
impl std::io::Write for Fnv {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
for byte in bytes {
self.0 ^= u64::from(*byte);
self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3);
}
Ok(bytes.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
pub(crate) struct Canonical<'a, T: ?Sized>(pub(crate) &'a T);
impl Serialize for Canonical<'_, serde_json::Map<String, serde_json::Value>> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut fields: Vec<_> = self.0.iter().collect();
fields.sort_by_key(|(left, _)| *left);
serializer.collect_map(
fields
.into_iter()
.map(|(key, value)| (key, Canonical(value))),
)
}
}
impl Serialize for Canonical<'_, serde_json::Value> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self.0 {
serde_json::Value::Object(fields) => Canonical(fields).serialize(serializer),
serde_json::Value::Array(values) => {
serializer.collect_seq(values.iter().map(Canonical))
}
serde_json::Value::Number(number) => match number
.as_f64()
.filter(|float| number.is_f64() && float.fract() == 0.0)
.and_then(|float| format!("{float:.0}").parse::<serde_json::Number>().ok())
{
Some(whole) => whole.serialize(serializer),
None => number.serialize(serializer),
},
value => value.serialize(serializer),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Native {
pub item: serde_json::Value,
pub fingerprint: Fingerprint,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub baseline: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "crate::json_utils::is_false")]
pub edited: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub blob_slot: Option<Cow<'static, str>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Blob<'a> {
pub key: &'a str,
pub value: &'a str,
}
impl Native {
pub fn reasoning_blob(&self) -> Option<Blob<'_>> {
let key = self.blob_slot.as_deref()?;
let value = self.item.pointer(key)?.as_str()?;
(!value.is_empty()).then_some(Blob { key, value })
}
pub(crate) fn find_blob(&mut self, slots: &[&'static str]) {
let holds = |slot: &&str| {
self.item
.pointer(slot)
.and_then(serde_json::Value::as_str)
.is_some_and(|value| !value.is_empty())
};
self.blob_slot = slots.iter().copied().find(holds).map(Cow::Borrowed);
}
}
pub(super) fn baseline(value: serde_json::Value) -> serde_json::Value {
use serde_json::Value;
match value {
Value::Number(number) if !number.is_f64() => Value::String(format!("i:{number}")),
Value::String(text) => Value::String(format!("s:{text}")),
Value::Array(values) => Value::Array(values.into_iter().map(baseline).collect()),
Value::Object(fields) => Value::Object(
fields
.into_iter()
.map(|(key, value)| (key, baseline(value)))
.collect(),
),
value => value,
}
}
pub(super) fn matches_baseline(saved: &serde_json::Value, current: &serde_json::Value) -> bool {
use serde_json::Value;
match (saved, current) {
(Value::String(saved), Value::Number(number)) => {
let Some(integer) = saved.strip_prefix("i:") else {
return false;
};
if number.is_f64() {
number
.as_f64()
.filter(|value| value.fract() == 0.0)
.is_some_and(|value| format!("{value:.0}") == integer)
} else {
number.to_string() == integer
}
}
(Value::String(saved), Value::String(current)) => {
saved.strip_prefix("s:") == Some(current.as_str())
}
(Value::Array(saved), Value::Array(current)) => {
saved.len() == current.len()
&& saved
.iter()
.zip(current)
.all(|(saved, current)| matches_baseline(saved, current))
}
(Value::Object(saved), Value::Object(current)) => {
saved.len() == current.len()
&& saved.iter().all(|(key, saved)| {
current
.get(key)
.is_some_and(|current| matches_baseline(saved, current))
})
}
(Value::Number(_), Value::Number(_))
| (Value::Bool(_), Value::Bool(_))
| (Value::Null, Value::Null) => saved == current,
_ => false,
}
}
pub(crate) fn lenient<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Option<Native>, D::Error> {
let value = Option::<serde_json::Value>::deserialize(deserializer)?;
Ok(value.and_then(|value| Native::deserialize(value).ok()))
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Opaque {
pub item: serde_json::Value,
pub replay: bool,
}
impl Opaque {
pub fn kind(&self) -> Option<&str> {
self.item.get("type").and_then(serde_json::Value::as_str)
}
}
#[cfg(test)]
mod tests;