Skip to main content

systemprompt_cli/commands/infrastructure/logs/trace/
mod.rs

1mod ai_artifacts;
2mod ai_display;
3mod ai_mcp;
4mod display;
5mod json;
6mod list;
7mod show;
8mod summary;
9
10pub use summary::{print_summary, SummaryContext};
11
12use anyhow::Result;
13use clap::Subcommand;
14use schemars::JsonSchema;
15use serde::{Deserialize, Serialize};
16use systemprompt_runtime::DatabaseContext;
17
18use super::types::{MessageRow, ToolCallRow};
19use crate::CliConfig;
20
21#[derive(Debug, Subcommand)]
22pub enum TraceCommands {
23    #[command(
24        about = "List recent traces",
25        after_help = "EXAMPLES:\n  systemprompt infra logs trace list\n  systemprompt infra logs \
26                      trace list --limit 50 --since 1h\n  systemprompt infra logs trace list \
27                      --agent researcher --status completed"
28    )]
29    List(list::ListArgs),
30
31    #[command(
32        about = "Show trace details",
33        after_help = "EXAMPLES:\n  systemprompt infra logs trace show abc123\n  systemprompt \
34                      infra logs trace show abc123 --verbose\n  systemprompt infra logs trace \
35                      show abc123 --steps --ai --mcp"
36    )]
37    Show(show::ShowArgs),
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
41pub struct TraceEventRow {
42    pub timestamp: String,
43    pub delta_ms: i64,
44    pub event_type: String,
45    pub details: String,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub latency_ms: Option<i64>,
48}
49
50#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
51pub struct AiSummaryRow {
52    pub request_count: i64,
53    pub total_tokens: i64,
54    pub input_tokens: i64,
55    pub output_tokens: i64,
56    pub cost_dollars: f64,
57    pub total_latency_ms: i64,
58}
59
60#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
61pub struct McpSummaryRow {
62    pub execution_count: i64,
63    pub total_execution_time_ms: i64,
64}
65
66#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
67pub struct StepSummaryRow {
68    pub total: i64,
69    pub completed: i64,
70    pub failed: i64,
71    pub pending: i64,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
75pub struct TraceViewOutput {
76    pub trace_id: String,
77    pub events: Vec<TraceEventRow>,
78    pub ai_summary: AiSummaryRow,
79    pub mcp_summary: McpSummaryRow,
80    pub step_summary: StepSummaryRow,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub task_id: Option<String>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub duration_ms: Option<i64>,
85    pub status: String,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
89pub struct TraceListRow {
90    pub trace_id: String,
91    pub timestamp: String,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub agent: Option<String>,
94    pub status: String,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub duration_ms: Option<i64>,
97    pub ai_requests: i64,
98    pub mcp_calls: i64,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
102pub struct TraceListOutput {
103    pub traces: Vec<TraceListRow>,
104    pub total: u64,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
108pub struct TaskInfoRow {
109    pub task_id: String,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub agent_name: Option<String>,
112    pub status: String,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub started_at: Option<String>,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub duration_ms: Option<i64>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
120pub struct StepRow {
121    pub step_number: i32,
122    pub step_type: String,
123    pub title: String,
124    pub status: String,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub duration_ms: Option<i64>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
130pub struct AiRequestRow {
131    pub request_id: String,
132    pub model: String,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub max_tokens: Option<i32>,
135    pub tokens: String,
136    pub cost: String,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub latency_ms: Option<i64>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
142pub struct ArtifactRow {
143    pub artifact_id: String,
144    pub artifact_type: String,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub name: Option<String>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub source: Option<String>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub tool_name: Option<String>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
154pub struct AiTraceOutput {
155    pub task_id: String,
156    pub task_info: TaskInfoRow,
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub user_input: Option<String>,
159    pub execution_steps: Vec<StepRow>,
160    pub ai_requests: Vec<AiRequestRow>,
161    pub mcp_executions: Vec<ToolCallRow>,
162    pub artifacts: Vec<ArtifactRow>,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub agent_response: Option<String>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
168pub struct AiLookupOutput {
169    pub request_id: String,
170    pub provider: String,
171    pub model: String,
172    pub input_tokens: i32,
173    pub output_tokens: i32,
174    pub cost_dollars: f64,
175    pub latency_ms: i64,
176    pub messages: Vec<MessageRow>,
177    pub linked_mcp_calls: Vec<ToolCallRow>,
178}
179
180pub async fn execute(command: TraceCommands, config: &CliConfig) -> Result<()> {
181    match command {
182        TraceCommands::List(args) => list::execute(args, config).await,
183        TraceCommands::Show(args) => show::execute(args).await,
184    }
185}
186
187pub async fn execute_with_pool(
188    command: TraceCommands,
189    db_ctx: &DatabaseContext,
190    config: &CliConfig,
191) -> Result<()> {
192    match command {
193        TraceCommands::List(args) => list::execute_with_pool(args, db_ctx, config).await,
194        TraceCommands::Show(args) => show::execute_with_pool(args, db_ctx).await,
195    }
196}