Skip to main content

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

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