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: 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#[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#[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#[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
174impl 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
195impl ProviderModelItem {
197 pub fn from_provider_metadata(provider_id: String, metadata: &ProviderMetadata) -> Vec<Self> {
198 match metadata {
199 ProviderMetadata::ModelProvider(model_provider) => {
200 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 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}