systemprompt_models/artifacts/
metadata.rs1use chrono::{DateTime, Utc};
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use serde_json::Value as JsonValue;
18use systemprompt_identifiers::{
19 AgentName, ArtifactId, ContextId, McpExecutionId, McpToolName, SessionId, SkillId, SkillName,
20 TaskId, TraceId, UserId,
21};
22
23use crate::execution::context::RequestContext;
24
25pub const EXECUTION_META_KEY: &str = "io.systemprompt/execution";
26
27#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
28pub struct ExecutionMetadata {
29 #[schemars(with = "String")]
30 pub context_id: ContextId,
31
32 #[schemars(with = "String")]
33 pub trace_id: TraceId,
34
35 #[schemars(with = "String")]
36 pub session_id: SessionId,
37
38 #[schemars(with = "String")]
39 pub user_id: UserId,
40
41 #[schemars(with = "String")]
42 pub agent_name: AgentName,
43
44 #[schemars(with = "String")]
45 pub timestamp: DateTime<Utc>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
48 #[schemars(with = "Option<String>")]
49 pub task_id: Option<TaskId>,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
52 #[schemars(with = "Option<String>")]
53 pub tool_name: Option<McpToolName>,
54
55 #[serde(skip_serializing_if = "Option::is_none")]
56 #[schemars(with = "Option<String>")]
57 pub skill_id: Option<SkillId>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
60 #[schemars(with = "Option<String>")]
61 pub skill_name: Option<SkillName>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub execution_id: Option<McpExecutionId>,
65}
66
67#[derive(Debug)]
68pub struct ExecutionMetadataBuilder {
69 context_id: ContextId,
70 trace_id: TraceId,
71 session_id: SessionId,
72 user_id: UserId,
73 agent_name: AgentName,
74 timestamp: DateTime<Utc>,
75 task_id: Option<TaskId>,
76 tool_name: Option<McpToolName>,
77 skill_id: Option<SkillId>,
78 skill_name: Option<SkillName>,
79 execution_id: Option<McpExecutionId>,
80}
81
82impl ExecutionMetadataBuilder {
83 pub fn new(ctx: &RequestContext) -> Self {
84 Self {
85 context_id: ctx.context_id().clone(),
86 trace_id: ctx.trace_id().clone(),
87 session_id: ctx.session_id().clone(),
88 user_id: ctx.user_id().clone(),
89 agent_name: ctx.agent_name().clone(),
90 timestamp: Utc::now(),
91 task_id: ctx.task_id().cloned(),
92 tool_name: None,
93 skill_id: None,
94 skill_name: None,
95 execution_id: None,
96 }
97 }
98
99 pub fn with_tool(mut self, name: McpToolName) -> Self {
100 self.tool_name = Some(name);
101 self
102 }
103
104 pub fn with_skill(mut self, id: SkillId, name: SkillName) -> Self {
105 self.skill_id = Some(id);
106 self.skill_name = Some(name);
107 self
108 }
109
110 pub fn with_execution(mut self, id: McpExecutionId) -> Self {
111 self.execution_id = Some(id);
112 self
113 }
114
115 pub fn build(self) -> ExecutionMetadata {
116 ExecutionMetadata {
117 context_id: self.context_id,
118 trace_id: self.trace_id,
119 session_id: self.session_id,
120 user_id: self.user_id,
121 agent_name: self.agent_name,
122 timestamp: self.timestamp,
123 task_id: self.task_id,
124 tool_name: self.tool_name,
125 skill_id: self.skill_id,
126 skill_name: self.skill_name,
127 execution_id: self.execution_id,
128 }
129 }
130}
131
132impl ExecutionMetadata {
133 pub fn builder(ctx: &RequestContext) -> ExecutionMetadataBuilder {
134 ExecutionMetadataBuilder::new(ctx)
135 }
136
137 pub fn with_request(ctx: &RequestContext) -> Self {
138 Self::builder(ctx).build()
139 }
140
141 pub fn with_tool(mut self, name: McpToolName) -> Self {
142 self.tool_name = Some(name);
143 self
144 }
145
146 pub fn with_skill(mut self, id: SkillId, name: SkillName) -> Self {
147 self.skill_id = Some(id);
148 self.skill_name = Some(name);
149 self
150 }
151
152 pub fn with_execution(mut self, id: McpExecutionId) -> Self {
153 self.execution_id = Some(id);
154 self
155 }
156
157 pub fn schema() -> JsonValue {
159 match serde_json::to_value(schemars::schema_for!(Self)) {
160 Ok(v) => v,
161 Err(e) => {
162 tracing::error!(error = %e, "ExecutionMetadata schema serialization failed");
163 JsonValue::Null
164 },
165 }
166 }
167
168 pub fn to_object(&self) -> Result<serde_json::Map<String, JsonValue>, serde_json::Error> {
170 serde_json::from_value(serde_json::to_value(self)?)
171 }
172}
173
174#[derive(Debug, Clone, Default)]
178pub struct ArtifactProvenance {
179 request: Option<ExecutionMetadata>,
180 execution_id: Option<McpExecutionId>,
181 skill: Option<(SkillId, SkillName)>,
182}
183
184impl ArtifactProvenance {
185 pub fn set_request(&mut self, ctx: &RequestContext) {
186 self.request = Some(ExecutionMetadata::with_request(ctx));
187 }
188
189 pub fn set_metadata(&mut self, metadata: ExecutionMetadata) {
190 self.request = Some(metadata);
191 }
192
193 pub fn set_execution_id(&mut self, id: McpExecutionId) {
194 self.execution_id = Some(id);
195 }
196
197 pub fn set_skill(&mut self, id: SkillId, name: SkillName) {
198 self.skill = Some((id, name));
199 }
200
201 pub fn execution_id(&self) -> Option<&McpExecutionId> {
202 self.execution_id.as_ref().or_else(|| {
203 self.request
204 .as_ref()
205 .and_then(|metadata| metadata.execution_id.as_ref())
206 })
207 }
208
209 pub fn metadata(&self) -> Option<ExecutionMetadata> {
210 let mut metadata = self.request.clone()?;
211 if let Some(id) = &self.execution_id {
212 metadata.execution_id = Some(id.clone());
213 }
214 if let Some((id, name)) = &self.skill {
215 metadata.skill_id = Some(id.clone());
216 metadata.skill_name = Some(name.clone());
217 }
218 Some(metadata)
219 }
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
223pub struct ToolResponse<T> {
224 pub artifact_id: ArtifactId,
225 pub mcp_execution_id: McpExecutionId,
226 pub artifact: T,
227 #[serde(rename = "_metadata")]
228 pub metadata: ExecutionMetadata,
229}
230
231impl<T: Serialize + JsonSchema> ToolResponse<T> {
232 pub const fn new(
233 artifact_id: ArtifactId,
234 mcp_execution_id: McpExecutionId,
235 artifact: T,
236 metadata: ExecutionMetadata,
237 ) -> Self {
238 Self {
239 artifact_id,
240 mcp_execution_id,
241 artifact,
242 metadata,
243 }
244 }
245
246 pub fn to_json(&self) -> Result<JsonValue, serde_json::Error> {
248 serde_json::to_value(self)
249 }
250}