Skip to main content

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

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