1pub mod deployment;
6pub mod metrics;
7pub mod openai;
8pub mod request;
9pub mod response;
10pub mod team;
11pub mod user;
12
13pub use team::*;
16pub use user::*;
17
18use serde::{Deserialize, Serialize};
19use std::collections::HashMap;
20use uuid::Uuid;
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Metadata {
25 pub id: Uuid,
27 pub created_at: chrono::DateTime<chrono::Utc>,
29 pub updated_at: chrono::DateTime<chrono::Utc>,
31 pub version: i64,
33 #[serde(default)]
35 pub extra: HashMap<String, serde_json::Value>,
36}
37
38impl Default for Metadata {
39 fn default() -> Self {
40 let now = chrono::Utc::now();
41 Self {
42 id: Uuid::new_v4(),
43 created_at: now,
44 updated_at: now,
45 version: 1,
46 extra: HashMap::new(),
47 }
48 }
49}
50
51impl Metadata {
52 pub fn new() -> Self {
54 Self::default()
55 }
56
57 pub fn touch(&mut self) {
59 self.updated_at = chrono::Utc::now();
60 self.version += 1;
61 }
62
63 pub fn set_extra<K: Into<String>, V: Into<serde_json::Value>>(&mut self, key: K, value: V) {
65 self.extra.insert(key.into(), value.into());
66 }
67
68 pub fn get_extra(&self, key: &str) -> Option<&serde_json::Value> {
70 self.extra.get(key)
71 }
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct ApiKey {
77 #[serde(flatten)]
79 pub metadata: Metadata,
80 pub name: String,
82 pub key_hash: String,
84 pub key_prefix: String,
86 pub user_id: Option<Uuid>,
88 pub team_id: Option<Uuid>,
90 pub permissions: Vec<String>,
92 pub rate_limits: Option<RateLimits>,
94 pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
96 pub is_active: bool,
98 pub last_used_at: Option<chrono::DateTime<chrono::Utc>>,
100 pub usage_stats: UsageStats,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct RateLimits {
107 pub rpm: Option<u32>,
109 pub tpm: Option<u32>,
111 pub rpd: Option<u32>,
113 pub tpd: Option<u32>,
115 pub concurrent: Option<u32>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, Default)]
121pub struct UsageStats {
122 pub total_requests: u64,
124 pub total_tokens: u64,
126 pub total_cost: f64,
128 pub requests_today: u32,
130 pub tokens_today: u32,
132 pub cost_today: f64,
134 pub last_reset: chrono::DateTime<chrono::Utc>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct ModelInfo {
141 pub id: String,
143 pub name: String,
145 pub provider: String,
147 pub model_type: ModelType,
149 pub context_window: Option<u32>,
151 pub max_output_tokens: Option<u32>,
153 pub input_cost_per_token: Option<f64>,
155 pub output_cost_per_token: Option<f64>,
157 pub features: Vec<String>,
159 pub description: Option<String>,
161 pub is_available: bool,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
167#[serde(rename_all = "snake_case")]
168pub enum ModelType {
169 Chat,
171 Completion,
173 Embedding,
175 ImageGeneration,
177 AudioTranscription,
179 AudioTranslation,
181 Moderation,
183 FineTuning,
185 Rerank,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct RequestContext {
192 pub request_id: String,
194 pub user_id: Option<Uuid>,
196 pub team_id: Option<Uuid>,
198 pub api_key_id: Option<Uuid>,
200 pub client_ip: Option<String>,
202 pub user_agent: Option<String>,
204 pub timestamp: chrono::DateTime<chrono::Utc>,
206 pub headers: HashMap<String, String>,
208 pub trace_id: Option<String>,
210 pub span_id: Option<String>,
212}
213
214impl Default for RequestContext {
215 fn default() -> Self {
216 Self {
217 request_id: Uuid::new_v4().to_string(),
218 user_id: None,
219 team_id: None,
220 api_key_id: None,
221 client_ip: None,
222 user_agent: None,
223 timestamp: chrono::Utc::now(),
224 headers: HashMap::new(),
225 trace_id: None,
226 span_id: None,
227 }
228 }
229}
230
231impl RequestContext {
232 pub fn new() -> Self {
234 Self::default()
235 }
236
237 pub fn with_user(mut self, user_id: Uuid, team_id: Option<Uuid>) -> Self {
239 self.user_id = Some(user_id);
240 self.team_id = team_id;
241 self
242 }
243
244 pub fn with_api_key(mut self, api_key_id: Uuid) -> Self {
246 self.api_key_id = Some(api_key_id);
247 self
248 }
249
250 pub fn with_client_info(mut self, ip: Option<String>, user_agent: Option<String>) -> Self {
252 self.client_ip = ip;
253 self.user_agent = user_agent;
254 self
255 }
256
257 pub fn with_tracing(mut self, trace_id: String, span_id: String) -> Self {
259 self.trace_id = Some(trace_id);
260 self.span_id = Some(span_id);
261 self
262 }
263
264 pub fn add_header<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
266 self.headers.insert(key.into(), value.into());
267 self
268 }
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct ProviderHealth {
274 pub provider: String,
276 pub status: HealthStatus,
278 pub last_check: chrono::DateTime<chrono::Utc>,
280 pub response_time_ms: Option<u64>,
282 pub error_message: Option<String>,
284 pub success_rate: f64,
286 pub total_requests: u64,
288 pub failed_requests: u64,
290}
291
292#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
294#[serde(rename_all = "lowercase")]
295pub enum HealthStatus {
296 Healthy,
298 Degraded,
300 Unhealthy,
302 Unknown,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct ProviderRegistryHealth {
309 pub total_count: usize,
311 pub healthy_count: usize,
313 pub degraded_count: usize,
315 pub unhealthy_count: usize,
317 pub providers: Vec<ProviderHealth>,
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn test_metadata_creation() {
327 let metadata = Metadata::new();
328 assert_eq!(metadata.version, 1);
329 assert!(metadata.created_at <= chrono::Utc::now());
330 }
331
332 #[test]
333 fn test_metadata_touch() {
334 let mut metadata = Metadata::new();
335 let original_version = metadata.version;
336 let original_updated = metadata.updated_at;
337
338 std::thread::sleep(std::time::Duration::from_millis(1));
339 metadata.touch();
340
341 assert_eq!(metadata.version, original_version + 1);
342 assert!(metadata.updated_at > original_updated);
343 }
344
345 #[test]
346 fn test_request_context_creation() {
347 let context = RequestContext::new();
348 assert!(!context.request_id.is_empty());
349 assert!(context.user_id.is_none());
350 }
351
352 #[test]
353 fn test_request_context_builder() {
354 let user_id = Uuid::new_v4();
355 let team_id = Uuid::new_v4();
356
357 let context = RequestContext::new()
358 .with_user(user_id, Some(team_id))
359 .with_client_info(
360 Some("127.0.0.1".to_string()),
361 Some("test-agent".to_string()),
362 )
363 .add_header("X-Custom", "value");
364
365 assert_eq!(context.user_id, Some(user_id));
366 assert_eq!(context.team_id, Some(team_id));
367 assert_eq!(context.client_ip, Some("127.0.0.1".to_string()));
368 assert_eq!(context.headers.get("X-Custom"), Some(&"value".to_string()));
369 }
370}