systemprompt_models/artifacts/
metadata.rs1use chrono::{DateTime, Utc};
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use serde_json::Value as JsonValue;
5use systemprompt_identifiers::{
6 AgentName, ArtifactId, ContextId, McpExecutionId, SessionId, SkillId, TaskId, TraceId, UserId,
7};
8
9use crate::execution::context::RequestContext;
10
11#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
12pub struct ExecutionMetadata {
13 #[schemars(with = "String")]
14 pub context_id: ContextId,
15
16 #[schemars(with = "String")]
17 pub trace_id: TraceId,
18
19 #[schemars(with = "String")]
20 pub session_id: SessionId,
21
22 #[schemars(with = "String")]
23 pub user_id: UserId,
24
25 #[schemars(with = "String")]
26 pub agent_name: AgentName,
27
28 #[schemars(with = "String")]
29 pub timestamp: DateTime<Utc>,
30
31 #[serde(skip_serializing_if = "Option::is_none")]
32 #[schemars(with = "Option<String>")]
33 pub task_id: Option<TaskId>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub tool_name: Option<String>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
39 #[schemars(with = "Option<String>")]
40 pub skill_id: Option<SkillId>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub skill_name: Option<String>,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub execution_id: Option<String>,
47}
48
49impl Default for ExecutionMetadata {
50 fn default() -> Self {
51 Self {
52 context_id: ContextId::new("default"),
53 trace_id: TraceId::new("default"),
54 session_id: SessionId::new("default"),
55 user_id: UserId::new("default"),
56 agent_name: AgentName::new("default"),
57 timestamp: Utc::now(),
58 task_id: None,
59 tool_name: None,
60 skill_id: None,
61 skill_name: None,
62 execution_id: None,
63 }
64 }
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<String>,
77 skill_id: Option<SkillId>,
78 skill_name: Option<String>,
79 execution_id: Option<String>,
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: impl Into<String>) -> Self {
100 self.tool_name = Some(name.into());
101 self
102 }
103
104 pub fn with_skill(mut self, id: impl Into<SkillId>, name: impl Into<String>) -> Self {
105 self.skill_id = Some(id.into());
106 self.skill_name = Some(name.into());
107 self
108 }
109
110 pub fn with_execution(mut self, id: impl Into<String>) -> Self {
111 self.execution_id = Some(id.into());
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: impl Into<String>) -> Self {
142 self.tool_name = Some(name.into());
143 self
144 }
145
146 pub fn with_skill(mut self, id: impl Into<String>, name: impl Into<String>) -> Self {
147 self.skill_id = Some(SkillId::new(id));
148 self.skill_name = Some(name.into());
149 self
150 }
151
152 pub fn with_execution(mut self, id: impl Into<String>) -> Self {
153 self.execution_id = Some(id.into());
154 self
155 }
156
157 pub fn schema() -> JsonValue {
158 match serde_json::to_value(schemars::schema_for!(Self)) {
159 Ok(v) => v,
160 Err(e) => {
161 tracing::error!(error = %e, "ExecutionMetadata schema serialization failed");
162 JsonValue::Null
163 },
164 }
165 }
166
167 pub fn to_meta(&self) -> Option<rmcp::model::Meta> {
168 serde_json::to_value(self)
169 .map_err(|e| {
170 tracing::warn!(error = %e, "ExecutionMetadata serialization failed");
171 e
172 })
173 .ok()
174 .and_then(|v| v.as_object().cloned())
175 .map(rmcp::model::Meta)
176 }
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
180pub struct ToolResponse<T> {
181 pub artifact_id: ArtifactId,
182 pub mcp_execution_id: McpExecutionId,
183 pub artifact: T,
184 #[serde(rename = "_metadata")]
185 pub metadata: ExecutionMetadata,
186}
187
188impl<T: Serialize + JsonSchema> ToolResponse<T> {
189 pub const fn new(
190 artifact_id: ArtifactId,
191 mcp_execution_id: McpExecutionId,
192 artifact: T,
193 metadata: ExecutionMetadata,
194 ) -> Self {
195 Self {
196 artifact_id,
197 mcp_execution_id,
198 artifact,
199 metadata,
200 }
201 }
202
203 pub fn to_json(&self) -> JsonValue {
204 match serde_json::to_value(self) {
205 Ok(value) => value,
206 Err(e) => {
207 tracing::error!(
208 error = %e,
209 artifact_id = %self.artifact_id,
210 "ToolResponse serialization failed"
211 );
212 JsonValue::Null
213 },
214 }
215 }
216
217 pub fn try_to_json(&self) -> Result<JsonValue, serde_json::Error> {
218 serde_json::to_value(self)
219 }
220}
221
222impl<T: JsonSchema> ToolResponse<T> {
223 pub fn schema() -> JsonValue {
224 match serde_json::to_value(schemars::schema_for!(Self)) {
225 Ok(v) => v,
226 Err(e) => {
227 tracing::error!(error = %e, "ToolResponse schema serialization failed");
228 JsonValue::Null
229 },
230 }
231 }
232}