use std::collections::HashMap;
use thiserror::Error;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum EnvelopeKind {
Audio(Vec<u8>),
Text(String),
Embedding(Vec<f32>),
}
impl EnvelopeKind {
pub fn as_str(&self) -> &'static str {
match self {
EnvelopeKind::Audio(_) => "Audio",
EnvelopeKind::Text(_) => "Text",
EnvelopeKind::Embedding(_) => "Embedding",
}
}
pub fn payload_size(&self) -> usize {
match self {
EnvelopeKind::Audio(data) => data.len(),
EnvelopeKind::Text(data) => data.len(),
EnvelopeKind::Embedding(data) => data.len() * std::mem::size_of::<f32>(),
}
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Envelope {
pub kind: EnvelopeKind,
pub metadata: HashMap<String, String>,
}
impl Envelope {
pub const LOCAL_ID_METADATA_KEY: &'static str = "xybrid.local_id";
pub fn new(kind: EnvelopeKind) -> Self {
let mut metadata = HashMap::new();
metadata.insert(
Self::LOCAL_ID_METADATA_KEY.to_string(),
Uuid::new_v4().to_string(),
);
Self { kind, metadata }
}
pub fn with_metadata(kind: EnvelopeKind, mut metadata: HashMap<String, String>) -> Self {
if !metadata.contains_key(Self::LOCAL_ID_METADATA_KEY) {
metadata.insert(
Self::LOCAL_ID_METADATA_KEY.to_string(),
Uuid::new_v4().to_string(),
);
}
Self { kind, metadata }
}
pub fn local_id(&self) -> &str {
self.metadata
.get(Self::LOCAL_ID_METADATA_KEY)
.map(|s| s.as_str())
.unwrap_or("")
}
pub fn with_local_id(mut self, id: impl Into<String>) -> Self {
self.metadata
.insert(Self::LOCAL_ID_METADATA_KEY.to_string(), id.into());
self
}
pub fn set_metadata(&mut self, key: String, value: String) {
self.metadata.insert(key, value);
}
pub fn get_metadata(&self, key: &str) -> Option<&String> {
self.metadata.get(key)
}
pub const ROLE_METADATA_KEY: &'static str = "xybrid.role";
pub fn with_role(mut self, role: super::MessageRole) -> Self {
self.metadata.insert(
Self::ROLE_METADATA_KEY.to_string(),
role.as_str().to_string(),
);
self
}
pub fn role(&self) -> Option<super::MessageRole> {
self.metadata
.get(Self::ROLE_METADATA_KEY)
.and_then(|s| match s.as_str() {
"system" => Some(super::MessageRole::System),
"user" => Some(super::MessageRole::User),
"assistant" => Some(super::MessageRole::Assistant),
_ => None,
})
}
pub fn is_user_message(&self) -> bool {
self.role() == Some(super::MessageRole::User)
}
pub fn is_assistant_message(&self) -> bool {
self.role() == Some(super::MessageRole::Assistant)
}
pub fn is_system_message(&self) -> bool {
self.role() == Some(super::MessageRole::System)
}
pub fn kind_str(&self) -> &'static str {
self.kind.as_str()
}
pub fn payload_size(&self) -> usize {
self.kind.payload_size()
}
pub fn to_bytes(&self) -> Result<Vec<u8>, EnvelopeError> {
bincode::serialize(self).map_err(|e| {
EnvelopeError::SerializationError(format!("Failed to serialize envelope: {}", e))
})
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, EnvelopeError> {
bincode::deserialize(bytes).map_err(|e| {
EnvelopeError::DeserializationError(format!("Failed to deserialize envelope: {}", e))
})
}
pub fn to_json(&self) -> Result<String, EnvelopeError> {
serde_json::to_string_pretty(self).map_err(|e| {
EnvelopeError::SerializationError(format!(
"Failed to serialize envelope to JSON: {}",
e
))
})
}
pub fn from_json(json: &str) -> Result<Self, EnvelopeError> {
serde_json::from_str(json).map_err(|e| {
EnvelopeError::DeserializationError(format!(
"Failed to deserialize envelope from JSON: {}",
e
))
})
}
pub fn to_audio_samples(&self) -> Result<Option<AudioSamples>, EnvelopeError> {
let audio_bytes = match &self.kind {
EnvelopeKind::Audio(bytes) => bytes,
_ => {
return Err(EnvelopeError::DeserializationError(
"Envelope is not Audio type".to_string(),
))
}
};
let format = self
.get_metadata("format")
.map(|s| s.as_str())
.unwrap_or("wav");
let sample_rate: u32 = self
.get_metadata("sample_rate")
.and_then(|s| s.parse().ok())
.unwrap_or(16000);
let channels: u32 = self
.get_metadata("channels")
.and_then(|s| s.parse().ok())
.unwrap_or(1);
match format {
"float32" => {
let num_samples = audio_bytes.len() / 4;
let mut samples = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let idx = i * 4;
if idx + 3 < audio_bytes.len() {
let sample = f32::from_le_bytes([
audio_bytes[idx],
audio_bytes[idx + 1],
audio_bytes[idx + 2],
audio_bytes[idx + 3],
]);
samples.push(sample);
}
}
Ok(Some(AudioSamples {
samples,
sample_rate,
channels,
}))
}
"pcm16" => {
let num_samples = audio_bytes.len() / 2;
let mut samples = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let idx = i * 2;
if idx + 1 < audio_bytes.len() {
let sample_i16 =
i16::from_le_bytes([audio_bytes[idx], audio_bytes[idx + 1]]);
samples.push(sample_i16 as f32 / 32768.0);
}
}
Ok(Some(AudioSamples {
samples,
sample_rate,
channels,
}))
}
_ => {
Ok(None)
}
}
}
pub fn audio_bytes(&self) -> Option<&[u8]> {
match &self.kind {
EnvelopeKind::Audio(bytes) => Some(bytes),
_ => None,
}
}
pub fn audio_format(&self) -> Option<&str> {
self.get_metadata("format").map(|s| s.as_str())
}
}
#[derive(Debug, Clone)]
pub struct AudioSamples {
pub samples: Vec<f32>,
pub sample_rate: u32,
pub channels: u32,
}
impl AudioSamples {
pub fn to_mono(&self) -> Self {
if self.channels <= 1 {
return self.clone();
}
let channels = self.channels as usize;
let mono_samples: Vec<f32> = self
.samples
.chunks(channels)
.map(|chunk| chunk.iter().sum::<f32>() / channels as f32)
.collect();
Self {
samples: mono_samples,
sample_rate: self.sample_rate,
channels: 1,
}
}
pub fn resample(&self, target_rate: u32) -> Self {
if self.sample_rate == target_rate {
return self.clone();
}
let ratio = target_rate as f32 / self.sample_rate as f32;
let target_len = (self.samples.len() as f32 * ratio) as usize;
let resampled: Vec<f32> = (0..target_len)
.map(|i| {
let source_idx = (i as f32 / ratio) as usize;
self.samples.get(source_idx).copied().unwrap_or(0.0)
})
.collect();
Self {
samples: resampled,
sample_rate: target_rate,
channels: self.channels,
}
}
pub fn prepare_for_asr(&self) -> Self {
self.to_mono().resample(16000)
}
}
#[derive(Error, Debug)]
pub enum EnvelopeError {
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Deserialization error: {0}")]
DeserializationError(String),
}
pub type EnvelopeResult<T> = Result<T, EnvelopeError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_envelope_kind_as_str() {
assert_eq!(EnvelopeKind::Audio(vec![]).as_str(), "Audio");
assert_eq!(EnvelopeKind::Text(String::new()).as_str(), "Text");
assert_eq!(EnvelopeKind::Embedding(vec![]).as_str(), "Embedding");
}
#[test]
fn test_envelope_kind_payload_size() {
let audio = EnvelopeKind::Audio(vec![0u8; 100]);
assert_eq!(audio.payload_size(), 100);
let text = EnvelopeKind::Text("hello".to_string());
assert_eq!(text.payload_size(), 5);
let embedding = EnvelopeKind::Embedding(vec![0.0f32; 10]);
assert_eq!(embedding.payload_size(), 10 * std::mem::size_of::<f32>());
}
#[test]
fn test_envelope_new() {
let envelope = Envelope::new(EnvelopeKind::Text("test".to_string()));
assert_eq!(envelope.kind, EnvelopeKind::Text("test".to_string()));
assert!(!envelope.local_id().is_empty());
assert_eq!(envelope.local_id().len(), 36); }
#[test]
fn test_envelope_unique_local_ids() {
let e1 = Envelope::new(EnvelopeKind::Text("same text".to_string()));
let e2 = Envelope::new(EnvelopeKind::Text("same text".to_string()));
assert_ne!(e1.local_id(), e2.local_id());
}
#[test]
fn test_envelope_with_local_id() {
let envelope =
Envelope::new(EnvelopeKind::Text("test".to_string())).with_local_id("custom-id-123");
assert_eq!(envelope.local_id(), "custom-id-123");
}
#[test]
fn test_envelope_with_metadata() {
let mut metadata = HashMap::new();
metadata.insert("key1".to_string(), "value1".to_string());
let envelope =
Envelope::with_metadata(EnvelopeKind::Audio(vec![1, 2, 3]), metadata.clone());
assert_eq!(envelope.get_metadata("key1"), Some(&"value1".to_string()));
assert!(!envelope.local_id().is_empty());
}
#[test]
fn test_envelope_with_metadata_preserves_local_id() {
let mut metadata = HashMap::new();
metadata.insert("key1".to_string(), "value1".to_string());
metadata.insert(
Envelope::LOCAL_ID_METADATA_KEY.to_string(),
"my-custom-id".to_string(),
);
let envelope = Envelope::with_metadata(EnvelopeKind::Audio(vec![1, 2, 3]), metadata);
assert_eq!(envelope.local_id(), "my-custom-id");
}
#[test]
fn test_envelope_metadata_operations() {
let mut envelope = Envelope::new(EnvelopeKind::Text("test".to_string()));
envelope.set_metadata("key1".to_string(), "value1".to_string());
assert_eq!(envelope.get_metadata("key1"), Some(&"value1".to_string()));
assert_eq!(envelope.get_metadata("nonexistent"), None);
}
#[test]
fn test_envelope_kind_str() {
let envelope = Envelope::new(EnvelopeKind::Audio(vec![]));
assert_eq!(envelope.kind_str(), "Audio");
}
#[test]
fn test_envelope_serialization() -> Result<(), EnvelopeError> {
let mut envelope = Envelope::new(EnvelopeKind::Text("hello world".to_string()));
envelope.set_metadata("stage".to_string(), "asr".to_string());
let bytes = envelope.to_bytes()?;
assert!(!bytes.is_empty());
let deserialized = Envelope::from_bytes(&bytes)?;
assert_eq!(deserialized.kind, envelope.kind);
assert_eq!(deserialized.metadata, envelope.metadata);
Ok(())
}
#[test]
fn test_envelope_json_serialization() -> Result<(), EnvelopeError> {
let mut envelope = Envelope::new(EnvelopeKind::Text("hello".to_string()));
envelope.set_metadata("key".to_string(), "value".to_string());
let json = envelope.to_json()?;
assert!(json.contains("hello"));
assert!(json.contains("key"));
let deserialized = Envelope::from_json(&json)?;
assert_eq!(deserialized.kind, envelope.kind);
assert_eq!(deserialized.metadata, envelope.metadata);
Ok(())
}
#[test]
fn test_envelope_audio_roundtrip() -> Result<(), EnvelopeError> {
let audio_data = vec![0u8, 1u8, 2u8, 3u8, 4u8];
let envelope = Envelope::new(EnvelopeKind::Audio(audio_data.clone()));
let bytes = envelope.to_bytes()?;
let deserialized = Envelope::from_bytes(&bytes)?;
match deserialized.kind {
EnvelopeKind::Audio(data) => assert_eq!(data, audio_data),
_ => panic!("Expected Audio variant"),
}
Ok(())
}
#[test]
fn test_envelope_embedding_roundtrip() -> Result<(), EnvelopeError> {
let embedding_data = vec![1.0f32, 2.0f32, 3.0f32];
let envelope = Envelope::new(EnvelopeKind::Embedding(embedding_data.clone()));
let bytes = envelope.to_bytes()?;
let deserialized = Envelope::from_bytes(&bytes)?;
match deserialized.kind {
EnvelopeKind::Embedding(data) => assert_eq!(data, embedding_data),
_ => panic!("Expected Embedding variant"),
}
Ok(())
}
#[test]
fn test_envelope_with_role_user() {
use super::super::MessageRole;
let envelope =
Envelope::new(EnvelopeKind::Text("Hello".to_string())).with_role(MessageRole::User);
assert_eq!(envelope.role(), Some(MessageRole::User));
assert!(envelope.is_user_message());
assert!(!envelope.is_assistant_message());
assert!(!envelope.is_system_message());
}
#[test]
fn test_envelope_with_role_assistant() {
use super::super::MessageRole;
let envelope = Envelope::new(EnvelopeKind::Text("Hi there!".to_string()))
.with_role(MessageRole::Assistant);
assert_eq!(envelope.role(), Some(MessageRole::Assistant));
assert!(!envelope.is_user_message());
assert!(envelope.is_assistant_message());
assert!(!envelope.is_system_message());
}
#[test]
fn test_envelope_with_role_system() {
use super::super::MessageRole;
let envelope = Envelope::new(EnvelopeKind::Text("You are helpful.".to_string()))
.with_role(MessageRole::System);
assert_eq!(envelope.role(), Some(MessageRole::System));
assert!(!envelope.is_user_message());
assert!(!envelope.is_assistant_message());
assert!(envelope.is_system_message());
}
#[test]
fn test_envelope_without_role() {
let envelope = Envelope::new(EnvelopeKind::Text("Plain message".to_string()));
assert_eq!(envelope.role(), None);
assert!(!envelope.is_user_message());
assert!(!envelope.is_assistant_message());
assert!(!envelope.is_system_message());
}
#[test]
fn test_envelope_role_roundtrip() {
use super::super::MessageRole;
for role in [
MessageRole::System,
MessageRole::User,
MessageRole::Assistant,
] {
let envelope = Envelope::new(EnvelopeKind::Text("test".to_string())).with_role(role);
assert_eq!(
envelope.role(),
Some(role),
"Round-trip failed for {:?}",
role
);
}
}
#[test]
fn test_envelope_role_metadata_key() {
use super::super::MessageRole;
let envelope =
Envelope::new(EnvelopeKind::Text("test".to_string())).with_role(MessageRole::User);
assert_eq!(
envelope.get_metadata(Envelope::ROLE_METADATA_KEY),
Some(&"user".to_string())
);
}
#[test]
fn test_envelope_role_serialization_roundtrip() -> Result<(), EnvelopeError> {
use super::super::MessageRole;
let envelope =
Envelope::new(EnvelopeKind::Text("Hello".to_string())).with_role(MessageRole::User);
let bytes = envelope.to_bytes()?;
let deserialized = Envelope::from_bytes(&bytes)?;
assert_eq!(deserialized.role(), Some(MessageRole::User));
let json = envelope.to_json()?;
let from_json = Envelope::from_json(&json)?;
assert_eq!(from_json.role(), Some(MessageRole::User));
Ok(())
}
}