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 {
fn sorted(value: serde_json::Value) -> serde_json::Value {
match value {
serde_json::Value::Object(fields) => {
let mut fields: Vec<_> = fields.into_iter().collect();
fields.sort_by(|(left, _), (right, _)| left.cmp(right));
serde_json::Value::Object(
fields
.into_iter()
.map(|(key, value)| (key, sorted(value)))
.collect(),
)
}
serde_json::Value::Array(values) => {
serde_json::Value::Array(values.into_iter().map(sorted).collect())
}
serde_json::Value::Number(number) => number
.as_f64()
.filter(|float| number.is_f64() && float.fract() == 0.0)
.and_then(|float| format!("{float:.0}").parse::<serde_json::Number>().ok())
.map_or(serde_json::Value::Number(number), serde_json::Value::Number),
value => value,
}
}
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(())
}
}
let mut hash = Fnv(0xcbf2_9ce4_8422_2325);
if let Ok(value) = serde_json::to_value(value) {
let _ = serde_json::to_writer(&mut hash, &sorted(value));
}
Self(hash.0)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Native {
pub item: serde_json::Value,
pub fingerprint: Fingerprint,
}
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;