Skip to main content

systemprompt_models/artifacts/
metadata.rs

1use 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) -> Result<JsonValue, serde_json::Error> {
204        serde_json::to_value(self)
205    }
206}
207
208impl<T: JsonSchema> ToolResponse<T> {
209    pub fn schema() -> JsonValue {
210        match serde_json::to_value(schemars::schema_for!(Self)) {
211            Ok(v) => v,
212            Err(e) => {
213                tracing::error!(error = %e, "ToolResponse schema serialization failed");
214                JsonValue::Null
215            },
216        }
217    }
218}