systemprompt_cli/commands/infrastructure/logs/request/
mod.rs1mod list;
7mod show;
8mod stats;
9
10use anyhow::Result;
11use clap::Subcommand;
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14use systemprompt_runtime::DatabaseContext;
15
16use super::types::{MessageRow, ToolCallRow};
17use crate::CliConfig;
18use crate::shared::{CommandOutput, render_result};
19use systemprompt_models::artifacts::NoticeLine;
20
21pub use stats::{RequestStatsOutput, build_request_stats};
22
23const REQUEST_LIST_COLUMNS: [&str; 8] = [
24 "request_id",
25 "timestamp",
26 "provider",
27 "model",
28 "tokens",
29 "cost",
30 "latency_ms",
31 "status",
32];
33
34#[must_use]
35pub fn build_request_list(rows: &[RequestListRow]) -> CommandOutput {
36 if rows.is_empty() {
37 return CommandOutput::message(vec![NoticeLine::new("info", "No AI requests found")]);
38 }
39 CommandOutput::table_of(REQUEST_LIST_COLUMNS.to_vec(), rows).with_title("AI Requests")
40}
41
42#[must_use]
43pub fn build_request_show(detail: &RequestShowOutput) -> CommandOutput {
44 CommandOutput::card_value("AI Request Details", detail)
45}
46
47#[must_use]
48pub fn request_show_not_found(request_id: &str) -> CommandOutput {
49 CommandOutput::message(vec![
50 NoticeLine::new("warning", format!("AI request not found: {request_id}")),
51 NoticeLine::new(
52 "info",
53 "Tip: Use 'systemprompt infra logs request list' to see recent requests",
54 ),
55 ])
56}
57
58#[derive(Debug, Subcommand)]
59pub enum RequestCommands {
60 #[command(
61 about = "Operational list of recent AI requests. For dashboard metrics (time range, model filter, CSV export), use `analytics requests list`",
62 after_help = "EXAMPLES:\n systemprompt infra logs request list\n systemprompt infra \
63 logs request list --model gpt-4 --since 1h"
64 )]
65 List(list::ListArgs),
66
67 #[command(
68 about = "Quick single-request view by request id (messages, linked MCP calls, status/error)",
69 after_help = "EXAMPLES:\n systemprompt infra logs request show abc123\n systemprompt \
70 infra logs request show abc123 --messages --tools"
71 )]
72 Show(show::ShowArgs),
73
74 #[command(
75 about = "Operational request aggregate with by-provider / by-model breakdown. For range/model-filtered dashboards with export, use `analytics requests stats`",
76 after_help = "EXAMPLES:\n systemprompt infra logs request stats\n systemprompt infra \
77 logs request stats --since 24h"
78 )]
79 Stats(stats::StatsArgs),
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
83pub struct RequestListRow {
84 pub request_id: String,
85 pub timestamp: String,
86 pub provider: String,
87 pub model: String,
88 pub tokens: String,
89 pub cost: String,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub latency_ms: Option<i64>,
92 pub status: String,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
96pub struct RequestShowOutput {
97 pub request_id: String,
98 pub provider: String,
99 pub model: String,
100 pub input_tokens: i32,
101 pub output_tokens: i32,
102 pub cost_dollars: f64,
103 pub latency_ms: i64,
104 pub status: String,
105 #[serde(skip_serializing_if = "Option::is_none")]
106 pub error_message: Option<String>,
107 pub messages: Vec<MessageRow>,
108 pub linked_mcp_calls: Vec<ToolCallRow>,
109}
110
111pub async fn execute(command: RequestCommands, config: &CliConfig) -> Result<()> {
112 match command {
113 RequestCommands::List(args) => {
114 let result = list::execute(args, config).await?;
115 render_result(&result);
116 Ok(())
117 },
118 RequestCommands::Show(args) => {
119 let result = show::execute(args, config).await?;
120 render_result(&result);
121 Ok(())
122 },
123 RequestCommands::Stats(args) => stats::execute(args, config).await,
124 }
125}
126
127pub async fn execute_with_pool(
128 command: RequestCommands,
129 db_ctx: &DatabaseContext,
130 config: &CliConfig,
131) -> Result<()> {
132 match command {
133 RequestCommands::List(args) => {
134 let result = list::execute_with_pool(args, db_ctx, config).await?;
135 render_result(&result);
136 Ok(())
137 },
138 RequestCommands::Show(args) => {
139 let result = show::execute_with_pool(args, db_ctx, config).await?;
140 render_result(&result);
141 Ok(())
142 },
143 RequestCommands::Stats(args) => stats::execute_with_pool(args, db_ctx, config).await,
144 }
145}