Skip to main content

systemprompt_models/a2a/
artifact_metadata.rs

1use chrono::Utc;
2use serde::{Deserialize, Serialize};
3use systemprompt_identifiers::{ContextId, TaskId};
4use systemprompt_traits::validation::{
5    MetadataValidation, Validate, ValidationError, ValidationResult,
6};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
9pub struct ArtifactMetadata {
10    pub artifact_type: String,
11    pub context_id: ContextId,
12    pub created_at: String,
13    pub task_id: TaskId,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub rendering_hints: Option<serde_json::Value>,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub source: Option<String>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub mcp_execution_id: Option<String>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub mcp_schema: Option<serde_json::Value>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub is_internal: Option<bool>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub fingerprint: Option<String>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub tool_name: Option<String>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub execution_index: Option<usize>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub skill_id: Option<String>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub skill_name: Option<String>,
34}
35
36impl ArtifactMetadata {
37    pub fn new(artifact_type: String, context_id: ContextId, task_id: TaskId) -> Self {
38        Self {
39            artifact_type,
40            context_id,
41            task_id,
42            created_at: Utc::now().to_rfc3339(),
43            rendering_hints: None,
44            source: Some("mcp_tool".to_string()),
45            mcp_execution_id: None,
46            mcp_schema: None,
47            is_internal: None,
48            fingerprint: None,
49            tool_name: None,
50            execution_index: None,
51            skill_id: None,
52            skill_name: None,
53        }
54    }
55
56    pub fn with_rendering_hints(mut self, hints: serde_json::Value) -> Self {
57        self.rendering_hints = Some(hints);
58        self
59    }
60
61    pub fn with_source(mut self, source: String) -> Self {
62        self.source = Some(source);
63        self
64    }
65
66    pub fn with_mcp_execution_id(mut self, id: String) -> Self {
67        self.mcp_execution_id = Some(id);
68        self
69    }
70
71    pub fn with_mcp_schema(mut self, schema: serde_json::Value) -> Self {
72        self.mcp_schema = Some(schema);
73        self
74    }
75
76    pub const fn with_is_internal(mut self, is_internal: bool) -> Self {
77        self.is_internal = Some(is_internal);
78        self
79    }
80
81    pub fn with_fingerprint(mut self, fingerprint: String) -> Self {
82        self.fingerprint = Some(fingerprint);
83        self
84    }
85
86    pub fn with_tool_name(mut self, tool_name: String) -> Self {
87        self.tool_name = Some(tool_name);
88        self
89    }
90
91    pub const fn with_execution_index(mut self, index: usize) -> Self {
92        self.execution_index = Some(index);
93        self
94    }
95
96    pub fn with_skill_id(mut self, skill_id: String) -> Self {
97        self.skill_id = Some(skill_id);
98        self
99    }
100
101    pub fn with_skill_name(mut self, skill_name: String) -> Self {
102        self.skill_name = Some(skill_name);
103        self
104    }
105
106    pub fn with_skill(mut self, skill_id: String, skill_name: String) -> Self {
107        self.skill_id = Some(skill_id);
108        self.skill_name = Some(skill_name);
109        self
110    }
111
112    pub fn new_validated(
113        artifact_type: String,
114        context_id: ContextId,
115        task_id: TaskId,
116    ) -> ValidationResult<Self> {
117        if artifact_type.is_empty() {
118            return Err(ValidationError::new(
119                "artifact_type",
120                "Cannot create ArtifactMetadata: artifact_type is empty",
121            )
122            .with_context(format!(
123                "artifact_type={artifact_type:?}, context_id={context_id:?}, task_id={task_id:?}"
124            )));
125        }
126
127        let metadata = Self {
128            artifact_type,
129            context_id,
130            task_id,
131            created_at: Utc::now().to_rfc3339(),
132            rendering_hints: None,
133            source: Some("mcp_tool".to_string()),
134            mcp_execution_id: None,
135            mcp_schema: None,
136            is_internal: None,
137            fingerprint: None,
138            tool_name: None,
139            execution_index: None,
140            skill_id: None,
141            skill_name: None,
142        };
143
144        metadata.validate()?;
145        Ok(metadata)
146    }
147}
148
149impl Validate for ArtifactMetadata {
150    fn validate(&self) -> ValidationResult<()> {
151        self.validate_required_fields()?;
152        Ok(())
153    }
154}
155
156impl MetadataValidation for ArtifactMetadata {
157    fn required_string_fields(&self) -> Vec<(&'static str, &str)> {
158        vec![
159            ("artifact_type", &self.artifact_type),
160            ("context_id", self.context_id.as_str()),
161            ("task_id", self.task_id.as_str()),
162            ("created_at", &self.created_at),
163        ]
164    }
165}