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