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