aimo_client/types/
statistics.rs

1use aimo_core::{
2    provider::{Model, ProviderMetadata},
3    receipt::RequestReceipt,
4};
5use serde::{Deserialize, Serialize};
6
7// Common pagination and filtering structures
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct PaginationQuery {
10    pub page: Option<String>,
11    pub limit: Option<String>,
12}
13
14impl PaginationQuery {
15    pub fn get_page(&self) -> u64 {
16        self.page
17            .as_ref()
18            .and_then(|p| p.parse().ok())
19            .unwrap_or(1)
20            .max(1)
21    }
22
23    pub fn get_limit(&self) -> u64 {
24        self.limit
25            .as_ref()
26            .and_then(|l| l.parse().ok())
27            .unwrap_or(20)
28            .min(100)
29            .max(1)
30    }
31}
32
33impl Default for PaginationQuery {
34    fn default() -> Self {
35        Self {
36            page: Some("1".to_string()),
37            limit: Some("20".to_string()),
38        }
39    }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct SortQuery {
44    pub sort_by: Option<String>,
45    pub sort_order: Option<SortOrder>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(rename_all = "lowercase")]
50pub enum SortOrder {
51    Asc,
52    Desc,
53}
54
55impl Default for SortOrder {
56    fn default() -> Self {
57        Self::Desc
58    }
59}
60
61// User activity endpoint types
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct UserActivityQuery {
64    #[serde(flatten)]
65    pub pagination: PaginationQuery,
66    #[serde(flatten)]
67    pub sort: SortQuery,
68    pub provider_id: Option<String>,
69    pub model_name: Option<String>,
70    pub from_timestamp: Option<String>,
71    pub to_timestamp: Option<String>,
72}
73
74impl UserActivityQuery {
75    pub fn get_from_timestamp(&self) -> Option<i64> {
76        self.from_timestamp.as_ref().and_then(|t| t.parse().ok())
77    }
78
79    pub fn get_to_timestamp(&self) -> Option<i64> {
80        self.to_timestamp.as_ref().and_then(|t| t.parse().ok())
81    }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct UserActivityItem {
86    pub timestamp: i64,
87    pub provider_id: String,
88    pub model_name: Option<String>,
89    pub prompt_tokens: u64,
90    pub completion_tokens: u64,
91    pub spending_token: String,
92    pub spending_amount: u64,
93    pub finish_reason: String,
94    pub request_id: String,
95    pub streamed: Option<bool>,
96    pub latency: Option<i64>,
97    pub generation_time: Option<i64>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct UserActivityResponse {
102    pub items: Vec<UserActivityItem>,
103    pub pagination: PaginationInfo,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct PaginationInfo {
108    pub page: u64,
109    pub limit: u64,
110    pub total_items: u64,
111    pub total_pages: u64,
112}
113
114// Provider models endpoint types
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct ProviderModelsQuery {
117    #[serde(flatten)]
118    pub pagination: PaginationQuery,
119    #[serde(flatten)]
120    pub sort: SortQuery,
121    pub provider_id: Option<String>,
122    pub provider_name: Option<String>,
123    pub model_name: Option<String>,
124    pub category: Option<String>,
125    pub include_stats: Option<bool>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct ProviderModelItem {
130    pub provider_id: String,
131    pub provider_name: String,
132    pub category: String,
133    pub model: Model,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub statistics: Option<ModelStatistics>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct ModelStatistics {
140    pub total_requests: u64,
141    pub total_tokens: u64,
142    pub avg_latency: f64,
143    pub avg_generation_time: f64,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct PriceInfo {
148    pub token: String,
149    pub input_price: u64,
150    pub output_price: u64,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct ProviderModelsResponse {
155    pub items: Vec<ProviderModelItem>,
156    pub pagination: PaginationInfo,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ModelDetail {
161    pub provider_id: String,
162    pub provider_name: String,
163    pub category: String,
164    pub model: Model,
165    pub statistics: Option<ModelStatistics>,
166}
167
168// Provider metadata endpoint types
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct ProviderMetadataResponse {
171    pub provider_id: String,
172    pub provider_name: String,
173    pub category: String,
174    pub models: Vec<ModelInfo>,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct ModelInfo {
179    pub name: String,
180    pub pricing: Vec<PriceInfo>,
181}
182
183// User balance endpoint types
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct UserBalanceResponse {
186    pub balance: Vec<UserBalanceItem>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct UserBalanceItem {
191    pub token: String,
192    pub available: u64,
193    pub locked: u64,
194    pub total: u64,
195}
196
197// Helper function to convert RequestSettled to UserActivityItem
198impl UserActivityItem {
199    pub fn from_request_settled(
200        receipt: RequestReceipt,
201        _provider_name: String,
202        model_name: Option<String>,
203    ) -> Self {
204        Self {
205            timestamp: receipt.timestamp,
206            provider_id: receipt.service_id.to_string(),
207            model_name,
208            prompt_tokens: receipt.prompt_tokens,
209            completion_tokens: receipt.completion_tokens,
210            spending_token: receipt.token_mint.to_string(),
211            spending_amount: receipt.amount,
212            finish_reason: receipt.finish_reason,
213            request_id: receipt.request_id.to_string(),
214            streamed: receipt.streamed,
215            latency: receipt.latency,
216            generation_time: receipt.generation_time,
217        }
218    }
219}
220
221// Helper function to convert ProviderMetadata to responses
222impl ProviderModelItem {
223    pub fn from_provider_metadata(
224        provider_id: String,
225        metadata: &ProviderMetadata,
226        statistics: Option<ModelStatistics>,
227    ) -> Vec<Self> {
228        match metadata {
229            ProviderMetadata::ModelProvider(model_provider) => {
230                // Properly serialize the category to match the serde snake_case format
231                let category = serde_json::to_value(&model_provider.category)
232                    .ok()
233                    .and_then(|v| v.as_str().map(|s| s.to_string()))
234                    .unwrap_or_else(|| "unknown".to_string());
235
236                model_provider
237                    .models
238                    .iter()
239                    .map(|model| Self {
240                        provider_id: provider_id.clone(),
241                        provider_name: model_provider.name.clone(),
242                        category: category.clone(),
243                        model: model.clone(),
244                        statistics: statistics.clone(),
245                    })
246                    .collect()
247            }
248        }
249    }
250}
251
252impl ProviderMetadataResponse {
253    pub fn from_provider_metadata(provider_id: String, metadata: ProviderMetadata) -> Self {
254        match metadata {
255            ProviderMetadata::ModelProvider(model_provider) => {
256                // Properly serialize the category to match the serde snake_case format
257                let category = serde_json::to_value(&model_provider.category)
258                    .ok()
259                    .and_then(|v| v.as_str().map(|s| s.to_string()))
260                    .unwrap_or_else(|| "unknown".to_string());
261
262                Self {
263                    provider_id,
264                    provider_name: model_provider.name,
265                    category,
266                    models: model_provider
267                        .models
268                        .into_iter()
269                        .map(|model| ModelInfo {
270                            name: model.name,
271                            pricing: model
272                                .pricing
273                                .into_iter()
274                                .map(|p| PriceInfo {
275                                    token: p.token,
276                                    input_price: p.input_price,
277                                    output_price: p.output_price,
278                                })
279                                .collect(),
280                        })
281                        .collect(),
282                }
283            }
284        }
285    }
286}