Skip to main content

distri_types/api/
usage.rs

1//! Wire-level request/response DTOs for the usage stats API.
2//!
3//! These types are shared between distri-cloud and distri-server so both
4//! services expose byte-identical JSON on the wire for the
5//! `GET /v1/usage/stats` endpoint.  Do not add server-specific logic here —
6//! this module is pure serde shapes.
7
8use chrono::{DateTime, Utc};
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use utoipa::ToSchema;
12use uuid::Uuid;
13
14/// Bucketing granularity for usage aggregation.
15///
16/// `None` means no bucketing — all matching records are collapsed into a
17/// single row.  The postgres-specific helper (`pg_trunc`) lives in
18/// `distri-cloud` as a free function and is **not** part of this type.
19#[derive(
20    Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default, ToSchema, JsonSchema,
21)]
22#[serde(rename_all = "lowercase")]
23pub enum Bucket {
24    #[default]
25    Day,
26    Week,
27    Month,
28    None,
29}
30
31/// Aggregated totals across the full query window.
32#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema, JsonSchema)]
33pub struct UsageTotals {
34    pub messages: i64,
35    pub input_tokens: i64,
36    pub output_tokens: i64,
37    pub cached_tokens: i64,
38    pub total_tokens: i64,
39    pub cost_usd: f64,
40}
41
42/// One time-bucket's aggregated usage.
43#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
44pub struct UsageBucket {
45    /// Bucket start timestamp, RFC3339. `None` when `bucket == None`.
46    pub ts: Option<String>,
47    pub messages: i64,
48    pub input_tokens: i64,
49    pub output_tokens: i64,
50    pub cached_tokens: i64,
51    pub total_tokens: i64,
52    pub cost_usd: f64,
53}
54
55/// Filters that were applied to produce the response, echoed back to the
56/// caller so clients can confirm defaults that were filled in server-side.
57#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
58pub struct AppliedFilters {
59    /// Cloud-only. Always `None` in distri-server (single-tenant).
60    pub user_id: Option<Uuid>,
61    /// Cloud-only. Always `None` in distri-server (single-tenant).
62    pub bot_id: Option<Uuid>,
63    /// Cloud-only. Always `None` in distri-server (single-tenant).
64    pub channel_id: Option<Uuid>,
65    pub thread_id: Option<String>,
66    pub agent_id: Option<String>,
67    /// RFC3339 timestamp (effective lower bound).
68    pub since: String,
69    /// RFC3339 timestamp (effective upper bound).
70    pub until: String,
71    pub bucket: Bucket,
72}
73
74/// Response body for `GET /v1/usage/stats`.
75#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
76pub struct UsageStatsResponse {
77    pub totals: UsageTotals,
78    pub buckets: Vec<UsageBucket>,
79    pub filters_applied: AppliedFilters,
80}
81
82/// Query parameters accepted by `GET /v1/usage/stats`.
83///
84/// `user_id`, `bot_id`, and `channel_id` are cloud-only filters; distri-server
85/// accepts (and silently ignores) them to maintain query-param parity.
86#[derive(Debug, Clone, Deserialize, ToSchema, JsonSchema)]
87pub struct UsageStatsQuery {
88    /// Cloud-only: filter by user.
89    pub user_id: Option<Uuid>,
90    /// Cloud-only: filter by bot.
91    pub bot_id: Option<Uuid>,
92    /// Cloud-only: filter by channel.
93    pub channel_id: Option<Uuid>,
94    pub thread_id: Option<String>,
95    pub agent_id: Option<String>,
96    pub since: Option<DateTime<Utc>>,
97    pub until: Option<DateTime<Utc>>,
98    pub bucket: Option<Bucket>,
99}