use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MessageMetadata {
pub timestamp: u64,
pub importance_score: f64,
pub compression_status: CompressionStatus,
pub estimated_tokens: usize,
pub source: Option<String>,
}
impl MessageMetadata {
pub fn user_input(timestamp: u64, estimated_tokens: usize) -> Self {
Self {
timestamp,
importance_score: 0.5,
compression_status: CompressionStatus::Uncompressed,
estimated_tokens,
source: Some("user_input".into()),
}
}
pub fn llm_response(timestamp: u64, estimated_tokens: usize) -> Self {
Self {
timestamp,
importance_score: 0.6,
compression_status: CompressionStatus::Uncompressed,
estimated_tokens,
source: Some("llm_response".into()),
}
}
pub fn tool_result(timestamp: u64, estimated_tokens: usize) -> Self {
Self {
timestamp,
importance_score: 0.4,
compression_status: CompressionStatus::Uncompressed,
estimated_tokens,
source: Some("tool_result".into()),
}
}
pub fn system(timestamp: u64, estimated_tokens: usize) -> Self {
Self {
timestamp,
importance_score: 1.0,
compression_status: CompressionStatus::Uncompressed,
estimated_tokens,
source: Some("system".into()),
}
}
pub fn synthetic(timestamp: u64, estimated_tokens: usize) -> Self {
Self {
timestamp,
importance_score: 0.3,
compression_status: CompressionStatus::Uncompressed,
estimated_tokens,
source: Some("synthetic".into()),
}
}
pub fn mark_compressed(&mut self, original_tokens: usize, compressed_tokens: usize) {
self.compression_status = CompressionStatus::Compressed {
original_token_count: original_tokens,
summary_token_count: compressed_tokens,
};
self.estimated_tokens = compressed_tokens;
}
pub fn mark_summarized(&mut self, original_tokens: usize, summary_tokens: usize) {
self.compression_status = CompressionStatus::Summarized {
original_token_count: original_tokens,
summary_token_count: summary_tokens,
};
self.estimated_tokens = summary_tokens;
}
pub fn set_importance(&mut self, score: f64) {
self.importance_score = score.clamp(0.0, 1.0);
}
pub fn original_token_count(&self) -> usize {
match self.compression_status {
CompressionStatus::Uncompressed => self.estimated_tokens,
CompressionStatus::Compressed {
original_token_count,
..
}
| CompressionStatus::Summarized {
original_token_count,
..
} => original_token_count,
CompressionStatus::Dropped => 0,
}
}
pub fn effective_token_count(&self) -> usize {
match self.compression_status {
CompressionStatus::Uncompressed => self.estimated_tokens,
CompressionStatus::Compressed {
summary_token_count,
..
}
| CompressionStatus::Summarized {
summary_token_count,
..
} => summary_token_count,
CompressionStatus::Dropped => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CompressionStatus {
Uncompressed,
Compressed {
original_token_count: usize,
summary_token_count: usize,
},
Summarized {
original_token_count: usize,
summary_token_count: usize,
},
Dropped,
}
impl Default for CompressionStatus {
fn default() -> Self {
Self::Uncompressed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_user_metadata() {
let meta = MessageMetadata::user_input(1000, 50);
assert_eq!(meta.timestamp, 1000);
assert!((meta.importance_score - 0.5).abs() < f64::EPSILON);
assert_eq!(meta.compression_status, CompressionStatus::Uncompressed);
assert_eq!(meta.estimated_tokens, 50);
assert_eq!(meta.source.as_deref(), Some("user_input"));
}
#[test]
fn test_create_llm_response_metadata() {
let meta = MessageMetadata::llm_response(2000, 150);
assert!((meta.importance_score - 0.6).abs() < f64::EPSILON);
}
#[test]
fn test_mark_compressed() {
let mut meta = MessageMetadata::user_input(1000, 200);
meta.mark_compressed(200, 50);
assert_eq!(meta.estimated_tokens, 50);
assert_eq!(meta.effective_token_count(), 50);
assert_eq!(meta.original_token_count(), 200);
}
#[test]
fn test_mark_summarized() {
let mut meta = MessageMetadata::user_input(1000, 300);
meta.mark_summarized(300, 30);
assert_eq!(meta.effective_token_count(), 30);
assert_eq!(meta.original_token_count(), 300);
}
#[test]
fn test_set_importance_clamps() {
let mut meta = MessageMetadata::user_input(1000, 50);
meta.set_importance(1.5);
assert!((meta.importance_score - 1.0).abs() < f64::EPSILON);
meta.set_importance(-0.5);
assert!((meta.importance_score - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_compression_status_serde_roundtrip() {
let status = CompressionStatus::Compressed {
original_token_count: 200,
summary_token_count: 50,
};
let json = serde_json::to_string(&status).unwrap();
let deserialized: CompressionStatus = serde_json::from_str(&json).unwrap();
assert_eq!(status, deserialized);
}
}