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