1use aimo_core::{
2 provider::{Model, ProviderMetadata},
3 receipt::RequestReceipt,
4};
5use serde::{Deserialize, Serialize};
6
7#[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#[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#[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#[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#[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
197impl 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
221impl 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 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 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}