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