enact_core/kernel/artifact/
metadata.rs1use crate::kernel::ids::{ArtifactId, ExecutionId, StepId};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum ArtifactType {
14 Text,
16 Code,
18 Json,
20 Image,
22 Pdf,
24 SearchResults,
26 ToolOutput,
28 Thought,
30 Plan,
32 Error,
34 Screenshot,
36 Audio,
38 Video,
40 Binary,
42}
43
44impl ArtifactType {
45 pub fn default_content_type(&self) -> &'static str {
47 match self {
48 Self::Text => "text/plain",
49 Self::Code => "text/plain",
50 Self::Json => "application/json",
51 Self::Image => "image/png",
52 Self::Pdf => "application/pdf",
53 Self::SearchResults => "application/json",
54 Self::ToolOutput => "application/json",
55 Self::Thought => "text/plain",
56 Self::Plan => "application/json",
57 Self::Error => "application/json",
58 Self::Screenshot => "image/png",
59 Self::Audio => "audio/wav",
60 Self::Video => "video/mp4",
61 Self::Binary => "application/octet-stream",
62 }
63 }
64}
65
66impl std::fmt::Display for ArtifactType {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 match self {
69 Self::Text => write!(f, "text"),
70 Self::Code => write!(f, "code"),
71 Self::Json => write!(f, "json"),
72 Self::Image => write!(f, "image"),
73 Self::Pdf => write!(f, "pdf"),
74 Self::SearchResults => write!(f, "search_results"),
75 Self::ToolOutput => write!(f, "tool_output"),
76 Self::Thought => write!(f, "thought"),
77 Self::Plan => write!(f, "plan"),
78 Self::Error => write!(f, "error"),
79 Self::Screenshot => write!(f, "screenshot"),
80 Self::Audio => write!(f, "audio"),
81 Self::Video => write!(f, "video"),
82 Self::Binary => write!(f, "binary"),
83 }
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
93#[serde(rename_all = "snake_case")]
94pub enum CompressionType {
95 None,
97 #[default]
99 Zstd,
100 Gzip,
102 Lz4,
104}
105
106impl CompressionType {
107 pub fn extension(&self) -> &'static str {
109 match self {
110 Self::None => "",
111 Self::Zstd => ".zst",
112 Self::Gzip => ".gz",
113 Self::Lz4 => ".lz4",
114 }
115 }
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct ArtifactMetadata {
125 pub artifact_id: ArtifactId,
127
128 pub execution_id: ExecutionId,
130
131 pub step_id: StepId,
133
134 pub name: String,
136
137 pub artifact_type: ArtifactType,
139
140 pub content_type: String,
142
143 pub original_size: u64,
145
146 pub compressed_size: u64,
148
149 pub compression: CompressionType,
151
152 pub content_hash: Option<String>,
154
155 pub storage_uri: Option<String>,
157
158 pub created_at: i64,
160
161 pub last_accessed_at: Option<i64>,
163
164 pub metadata: Option<serde_json::Value>,
166}
167
168impl ArtifactMetadata {
169 pub fn new(
171 artifact_id: ArtifactId,
172 execution_id: ExecutionId,
173 step_id: StepId,
174 name: impl Into<String>,
175 artifact_type: ArtifactType,
176 ) -> Self {
177 Self {
178 artifact_id,
179 execution_id,
180 step_id,
181 name: name.into(),
182 artifact_type,
183 content_type: artifact_type.default_content_type().to_string(),
184 original_size: 0,
185 compressed_size: 0,
186 compression: CompressionType::None,
187 content_hash: None,
188 storage_uri: None,
189 created_at: chrono::Utc::now().timestamp_millis(),
190 last_accessed_at: None,
191 metadata: None,
192 }
193 }
194
195 pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
197 self.content_type = content_type.into();
198 self
199 }
200
201 pub fn with_original_size(mut self, size: u64) -> Self {
203 self.original_size = size;
204 self
205 }
206
207 pub fn with_compressed_size(mut self, size: u64) -> Self {
209 self.compressed_size = size;
210 self
211 }
212
213 pub fn with_compression(mut self, compression: CompressionType) -> Self {
215 self.compression = compression;
216 self
217 }
218
219 pub fn with_content_hash(mut self, hash: impl Into<String>) -> Self {
221 self.content_hash = Some(hash.into());
222 self
223 }
224
225 pub fn with_storage_uri(mut self, uri: impl Into<String>) -> Self {
227 self.storage_uri = Some(uri.into());
228 self
229 }
230
231 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
233 self.metadata = Some(metadata);
234 self
235 }
236
237 pub fn compression_ratio(&self) -> f64 {
239 if self.original_size == 0 {
240 1.0
241 } else {
242 self.compressed_size as f64 / self.original_size as f64
243 }
244 }
245
246 pub fn space_savings_percent(&self) -> f64 {
248 (1.0 - self.compression_ratio()) * 100.0
249 }
250}
251
252#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_artifact_type_content_types() {
262 assert_eq!(ArtifactType::Text.default_content_type(), "text/plain");
263 assert_eq!(
264 ArtifactType::Json.default_content_type(),
265 "application/json"
266 );
267 assert_eq!(ArtifactType::Image.default_content_type(), "image/png");
268 assert_eq!(ArtifactType::Pdf.default_content_type(), "application/pdf");
269 }
270
271 #[test]
272 fn test_compression_ratio() {
273 let metadata = ArtifactMetadata::new(
274 ArtifactId::new(),
275 ExecutionId::new(),
276 StepId::new(),
277 "test.txt",
278 ArtifactType::Text,
279 )
280 .with_original_size(1000)
281 .with_compressed_size(300);
282
283 assert!((metadata.compression_ratio() - 0.3).abs() < 0.01);
284 assert!((metadata.space_savings_percent() - 70.0).abs() < 0.1);
285 }
286}