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