systemprompt_runtime/trace/repository/
mcp_trace.rs1use systemprompt_identifiers::{
7 AiRequestId, ArtifactId, ContextId, McpExecutionId, McpServerId, McpToolName, TaskId,
8};
9
10use super::{Result, TraceRepository};
11use crate::trace::models::{AiRequestInfo, McpToolExecution, TaskArtifact, ToolLogEntry};
12
13impl TraceRepository {
14 pub async fn fetch_mcp_executions(
15 &self,
16 task_id: &TaskId,
17 context_id: &ContextId,
18 ) -> Result<Vec<McpToolExecution>> {
19 let rows = sqlx::query!(
20 r#"SELECT mcp_execution_id, tool_name, server_name, status, execution_time_ms,
21 error_message, input, output
22 FROM mcp_tool_executions
23 WHERE task_id = $1 OR context_id = $2
24 ORDER BY started_at"#,
25 task_id.as_str(),
26 context_id.as_str()
27 )
28 .fetch_all(&*self.pool)
29 .await?;
30
31 Ok(rows
32 .into_iter()
33 .map(|r| McpToolExecution {
34 mcp_execution_id: McpExecutionId::new(r.mcp_execution_id),
35 tool_name: McpToolName::new(r.tool_name),
36 server_name: McpServerId::new(r.server_name),
37 status: r.status,
38 execution_time_ms: r.execution_time_ms,
39 error_message: r.error_message,
40 input: r.input,
41 output: r.output,
42 })
43 .collect())
44 }
45
46 pub async fn fetch_mcp_linked_ai_requests(
47 &self,
48 mcp_execution_id: &McpExecutionId,
49 ) -> Result<Vec<AiRequestInfo>> {
50 let rows = sqlx::query!(
51 r#"SELECT id, model, provider, max_tokens, input_tokens, output_tokens, cost_microdollars, latency_ms
52 FROM ai_requests
53 WHERE mcp_execution_id = $1
54 ORDER BY created_at"#,
55 mcp_execution_id.as_str()
56 )
57 .fetch_all(&*self.pool)
58 .await?;
59
60 Ok(rows
61 .into_iter()
62 .map(|r| AiRequestInfo {
63 id: AiRequestId::new(r.id),
64 provider: r.provider,
65 model: r.model,
66 max_tokens: r.max_tokens,
67 input_tokens: r.input_tokens,
68 output_tokens: r.output_tokens,
69 cost_microdollars: r.cost_microdollars,
70 latency_ms: r.latency_ms,
71 })
72 .collect())
73 }
74
75 pub async fn fetch_tool_logs(
76 &self,
77 task_id: &TaskId,
78 context_id: &ContextId,
79 ) -> Result<Vec<ToolLogEntry>> {
80 let rows = sqlx::query!(
81 r#"SELECT timestamp, level, module, message
82 FROM logs
83 WHERE (task_id = $1 OR context_id = $2)
84 AND (
85 (module LIKE '%_tools' OR module LIKE '%_manager' OR module LIKE 'create_%' OR module LIKE 'update_%' OR module LIKE 'research_%')
86 OR (level = 'ERROR' AND message LIKE '%tool%')
87 OR message LIKE 'Tool executed%'
88 OR message LIKE 'Tool failed%'
89 OR message LIKE 'MCP execution%'
90 )
91 ORDER BY timestamp"#,
92 task_id.as_str(),
93 context_id.as_str()
94 )
95 .fetch_all(&*self.pool)
96 .await?;
97
98 rows.into_iter()
99 .map(|r| -> Result<ToolLogEntry> {
100 Ok(ToolLogEntry {
101 timestamp: r.timestamp,
102 level: r.level.parse()?,
103 module: r.module,
104 message: r.message,
105 })
106 })
107 .collect()
108 }
109
110 pub async fn fetch_task_artifacts(
111 &self,
112 task_id: &TaskId,
113 context_id: &ContextId,
114 ) -> Result<Vec<TaskArtifact>> {
115 let rows = sqlx::query!(
116 r#"SELECT ta.artifact_id, ta.artifact_type, ta.name, ta.source, ta.tool_name,
117 ap.part_kind as "part_kind?", ap.text_content as "text_content?",
118 ap.data_content as "data_content?"
119 FROM task_artifacts ta
120 LEFT JOIN artifact_parts ap ON ta.artifact_id = ap.artifact_id AND ta.context_id = ap.context_id
121 WHERE ta.task_id = $1 OR ta.context_id = $2
122 ORDER BY ta.created_at, ap.sequence_number"#,
123 task_id.as_str(),
124 context_id.as_str()
125 )
126 .fetch_all(&*self.pool)
127 .await?;
128
129 Ok(rows
130 .into_iter()
131 .map(|r| TaskArtifact {
132 artifact_id: ArtifactId::new(r.artifact_id),
133 artifact_type: r.artifact_type,
134 name: r.name,
135 source: r.source,
136 tool_name: r.tool_name.map(McpToolName::new),
137 part_kind: r.part_kind,
138 text_content: r.text_content,
139 data_content: r.data_content,
140 })
141 .collect())
142 }
143}