use crate::kernel::ids::{ArtifactId, ExecutionId, StepId};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ArtifactType {
Text,
Code,
Json,
Image,
Pdf,
SearchResults,
ToolOutput,
Thought,
Plan,
Error,
Screenshot,
Audio,
Video,
Binary,
}
impl ArtifactType {
pub fn default_content_type(&self) -> &'static str {
match self {
Self::Text => "text/plain",
Self::Code => "text/plain",
Self::Json => "application/json",
Self::Image => "image/png",
Self::Pdf => "application/pdf",
Self::SearchResults => "application/json",
Self::ToolOutput => "application/json",
Self::Thought => "text/plain",
Self::Plan => "application/json",
Self::Error => "application/json",
Self::Screenshot => "image/png",
Self::Audio => "audio/wav",
Self::Video => "video/mp4",
Self::Binary => "application/octet-stream",
}
}
}
impl std::fmt::Display for ArtifactType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Text => write!(f, "text"),
Self::Code => write!(f, "code"),
Self::Json => write!(f, "json"),
Self::Image => write!(f, "image"),
Self::Pdf => write!(f, "pdf"),
Self::SearchResults => write!(f, "search_results"),
Self::ToolOutput => write!(f, "tool_output"),
Self::Thought => write!(f, "thought"),
Self::Plan => write!(f, "plan"),
Self::Error => write!(f, "error"),
Self::Screenshot => write!(f, "screenshot"),
Self::Audio => write!(f, "audio"),
Self::Video => write!(f, "video"),
Self::Binary => write!(f, "binary"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum CompressionType {
None,
#[default]
Zstd,
Gzip,
Lz4,
}
impl CompressionType {
pub fn extension(&self) -> &'static str {
match self {
Self::None => "",
Self::Zstd => ".zst",
Self::Gzip => ".gz",
Self::Lz4 => ".lz4",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArtifactMetadata {
pub artifact_id: ArtifactId,
pub execution_id: ExecutionId,
pub step_id: StepId,
pub name: String,
pub artifact_type: ArtifactType,
pub content_type: String,
pub original_size: u64,
pub compressed_size: u64,
pub compression: CompressionType,
pub content_hash: Option<String>,
pub storage_uri: Option<String>,
pub created_at: i64,
pub last_accessed_at: Option<i64>,
pub metadata: Option<serde_json::Value>,
}
impl ArtifactMetadata {
pub fn new(
artifact_id: ArtifactId,
execution_id: ExecutionId,
step_id: StepId,
name: impl Into<String>,
artifact_type: ArtifactType,
) -> Self {
Self {
artifact_id,
execution_id,
step_id,
name: name.into(),
artifact_type,
content_type: artifact_type.default_content_type().to_string(),
original_size: 0,
compressed_size: 0,
compression: CompressionType::None,
content_hash: None,
storage_uri: None,
created_at: chrono::Utc::now().timestamp_millis(),
last_accessed_at: None,
metadata: None,
}
}
pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = content_type.into();
self
}
pub fn with_original_size(mut self, size: u64) -> Self {
self.original_size = size;
self
}
pub fn with_compressed_size(mut self, size: u64) -> Self {
self.compressed_size = size;
self
}
pub fn with_compression(mut self, compression: CompressionType) -> Self {
self.compression = compression;
self
}
pub fn with_content_hash(mut self, hash: impl Into<String>) -> Self {
self.content_hash = Some(hash.into());
self
}
pub fn with_storage_uri(mut self, uri: impl Into<String>) -> Self {
self.storage_uri = Some(uri.into());
self
}
pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = Some(metadata);
self
}
pub fn compression_ratio(&self) -> f64 {
if self.original_size == 0 {
1.0
} else {
self.compressed_size as f64 / self.original_size as f64
}
}
pub fn space_savings_percent(&self) -> f64 {
(1.0 - self.compression_ratio()) * 100.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_artifact_type_content_types() {
assert_eq!(ArtifactType::Text.default_content_type(), "text/plain");
assert_eq!(
ArtifactType::Json.default_content_type(),
"application/json"
);
assert_eq!(ArtifactType::Image.default_content_type(), "image/png");
assert_eq!(ArtifactType::Pdf.default_content_type(), "application/pdf");
}
#[test]
fn test_compression_ratio() {
let metadata = ArtifactMetadata::new(
ArtifactId::new(),
ExecutionId::new(),
StepId::new(),
"test.txt",
ArtifactType::Text,
)
.with_original_size(1000)
.with_compressed_size(300);
assert!((metadata.compression_ratio() - 0.3).abs() < 0.01);
assert!((metadata.space_savings_percent() - 70.0).abs() < 0.1);
}
}