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: u8,
94    pub request_id: String,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct UserActivityResponse {
99    pub items: Vec<UserActivityItem>,
100    pub pagination: PaginationInfo,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct PaginationInfo {
105    pub page: u64,
106    pub limit: u64,
107    pub total_items: u64,
108    pub total_pages: u64,
109}
110
111// Provider models endpoint types
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ProviderModelsQuery {
114    #[serde(flatten)]
115    pub pagination: PaginationQuery,
116    #[serde(flatten)]
117    pub sort: SortQuery,
118    pub provider_id: Option<String>,
119    pub provider_name: Option<String>,
120    pub model_name: Option<String>,
121    pub category: Option<String>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ProviderModelItem {
126    pub provider_id: String,
127    pub provider_name: String,
128    pub category: String,
129    pub model: Model,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct PriceInfo {
134    pub token: String,
135    pub input_price: u64,
136    pub output_price: u64,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct ProviderModelsResponse {
141    pub items: Vec<ProviderModelItem>,
142    pub pagination: PaginationInfo,
143}
144
145// Provider metadata endpoint types
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct ProviderMetadataResponse {
148    pub provider_id: String,
149    pub provider_name: String,
150    pub category: String,
151    pub models: Vec<ModelInfo>,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct ModelInfo {
156    pub name: String,
157    pub pricing: Vec<PriceInfo>,
158}
159
160// User balance endpoint types
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct UserBalanceResponse {
163    pub balance: Vec<UserBalanceItem>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct UserBalanceItem {
168    pub token: String,
169    pub available: u64,
170    pub locked: u64,
171    pub total: u64,
172}
173
174// Helper function to convert RequestSettled to UserActivityItem
175impl UserActivityItem {
176    pub fn from_request_settled(
177        receipt: RequestReceipt,
178        _provider_name: String,
179        model_name: Option<String>,
180    ) -> Self {
181        Self {
182            timestamp: receipt.timestamp,
183            provider_id: receipt.service_id.to_string(),
184            model_name,
185            prompt_tokens: receipt.prompt_tokens,
186            completion_tokens: receipt.completion_tokens,
187            spending_token: receipt.token_mint.to_string(),
188            spending_amount: receipt.amount,
189            finish_reason: receipt.finish_reason,
190            request_id: receipt.request_id.to_string(),
191        }
192    }
193}
194
195// Helper function to convert ProviderMetadata to responses
196impl ProviderModelItem {
197    pub fn from_provider_metadata(provider_id: String, metadata: &ProviderMetadata) -> Vec<Self> {
198        match metadata {
199            ProviderMetadata::ModelProvider(model_provider) => {
200                // Properly serialize the category to match the serde snake_case format
201                let category = serde_json::to_value(&model_provider.category)
202                    .ok()
203                    .and_then(|v| v.as_str().map(|s| s.to_string()))
204                    .unwrap_or_else(|| "unknown".to_string());
205
206                model_provider
207                    .models
208                    .iter()
209                    .map(|model| Self {
210                        provider_id: provider_id.clone(),
211                        provider_name: model_provider.name.clone(),
212                        category: category.clone(),
213                        model: model.clone(),
214                    })
215                    .collect()
216            }
217        }
218    }
219}
220
221impl ProviderMetadataResponse {
222    pub fn from_provider_metadata(provider_id: String, metadata: ProviderMetadata) -> Self {
223        match metadata {
224            ProviderMetadata::ModelProvider(model_provider) => {
225                // Properly serialize the category to match the serde snake_case format
226                let category = serde_json::to_value(&model_provider.category)
227                    .ok()
228                    .and_then(|v| v.as_str().map(|s| s.to_string()))
229                    .unwrap_or_else(|| "unknown".to_string());
230
231                Self {
232                    provider_id,
233                    provider_name: model_provider.name,
234                    category,
235                    models: model_provider
236                        .models
237                        .into_iter()
238                        .map(|model| ModelInfo {
239                            name: model.name,
240                            pricing: model
241                                .pricing
242                                .into_iter()
243                                .map(|p| PriceInfo {
244                                    token: p.token,
245                                    input_price: p.input_price,
246                                    output_price: p.output_price,
247                                })
248                                .collect(),
249                        })
250                        .collect(),
251                }
252            }
253        }
254    }
255}