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,
}
const REASONING_BLOBS: [(&str, &str); 7] = [
("anthropic.messages", "/signature"),
("anthropic.messages", "/data"),
(
"bedrock.converse",
"/reasoningContent/reasoningText/signature",
),
("bedrock.converse", "/reasoningContent/redactedContent"),
("openai.responses", "/encrypted_content"),
("gemini.generate_content", "/thoughtSignature"),
("vertexai.generate_content", "/thoughtSignature"),
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Blob<'a> {
pub key: &'static str,
pub value: &'a str,
}
impl Native {
pub fn reasoning_blob(&self, api: &Api) -> Option<Blob<'_>> {
REASONING_BLOBS
.iter()
.filter(|(wire, _)| *wire == api.as_str())
.find_map(|(_, key)| {
let value = self.item.pointer(key)?.as_str()?;
(!value.is_empty()).then_some(Blob { key, value })
})
}
}
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;