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