oxios_kernel/token_maxing/
session.rs1use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10
11use super::quota_tracker::QuotaTrackerSnapshot;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct MaxingWindow {
16 pub start: DateTime<Utc>,
18 pub end: DateTime<Utc>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub enum MaxingStart {
25 Scheduled(MaxingWindow),
27 Manual,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33#[serde(rename_all = "lowercase")]
34pub enum TaskSource {
35 Skill,
37 Project,
39 Recurring,
41}
42
43#[derive(Debug, Clone, Default, Serialize, Deserialize)]
45pub struct ProviderWindowRecord {
46 pub started: DateTime<Utc>,
47 pub ended_at: Option<DateTime<Utc>>,
48}
49
50#[derive(Debug, Clone, Default, Serialize, Deserialize)]
52pub struct ProviderSessionRecord {
53 pub provider: String,
54 pub models_used: Vec<String>,
55 pub tasks_run: u64,
56 pub tokens_consumed: u64,
57 pub windows_drained: Vec<ProviderWindowRecord>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct TaskRecord {
63 pub source: TaskSource,
64 pub source_name: String,
65 pub goal: String,
66 pub provider: String,
67 pub model: String,
68 pub success: bool,
69 pub tokens: u64,
70 pub duration_secs: f64,
71 pub summary: String,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
78#[serde(rename_all = "snake_case")]
79pub enum StopReason {
80 WindowEnded,
82 NoWork,
84 Cancelled,
86}
87
88#[derive(Debug, Clone, Default, Serialize, Deserialize)]
90pub struct SessionTotals {
91 pub tasks: u64,
92 pub tokens: u64,
93 pub providers_fully_drained: u64,
94 pub resets_observed: u64,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct TokenMaxingSession {
100 pub id: String,
101 pub started_at: DateTime<Utc>,
102 pub ended_at: Option<DateTime<Utc>>,
103 pub window: Option<MaxingWindow>,
104 pub manual: bool,
105 pub providers: Vec<ProviderSessionRecord>,
106 pub tasks: Vec<TaskRecord>,
107 pub totals: SessionTotals,
108 pub stop_reason: Option<StopReason>,
109}
110
111impl TokenMaxingSession {
112 pub fn start(window: Option<MaxingWindow>, manual: bool) -> Self {
114 let now = Utc::now();
115 let id = format!("tm-{}", now.timestamp_millis());
116 Self {
117 id,
118 started_at: now,
119 ended_at: None,
120 window,
121 manual,
122 providers: Vec::new(),
123 tasks: Vec::new(),
124 totals: SessionTotals::default(),
125 stop_reason: None,
126 }
127 }
128
129 pub fn within_window(&self) -> bool {
131 match &self.window {
132 Some(w) => Utc::now() < w.end,
133 None => true,
134 }
135 }
136
137 #[allow(clippy::too_many_arguments)]
139 pub fn record_task(
140 &mut self,
141 source: TaskSource,
142 source_name: String,
143 goal: String,
144 provider: String,
145 model: String,
146 success: bool,
147 tokens: u64,
148 duration_secs: f64,
149 summary: String,
150 ) {
151 if !self.providers.iter().any(|r| r.provider == provider) {
154 self.providers.push(ProviderSessionRecord {
155 provider: provider.clone(),
156 ..Default::default()
157 });
158 }
159 let rec = self
160 .providers
161 .iter_mut()
162 .find(|r| r.provider == provider)
163 .expect("provider record just ensured to exist");
164 rec.tasks_run += 1;
165 rec.tokens_consumed += tokens;
166 if !rec.models_used.contains(&model) {
167 rec.models_used.push(model.clone());
168 }
169
170 self.tasks.push(TaskRecord {
171 source,
172 source_name,
173 goal,
174 provider,
175 model,
176 success,
177 tokens,
178 duration_secs,
179 summary,
180 });
181 self.totals.tasks += 1;
182 self.totals.tokens += tokens;
183 }
184
185 pub fn finalize(&mut self, reason: StopReason) {
187 self.ended_at = Some(Utc::now());
188 self.stop_reason = Some(reason);
189 }
190}
191
192#[derive(Debug, Clone, Serialize)]
194pub struct MaxerStatus {
195 pub running: bool,
196 pub current_session_id: Option<String>,
197 pub current_provider: Option<String>,
198 pub current_task: Option<String>,
199 pub manual: bool,
200 pub window: Option<MaxingWindow>,
201 pub tokens_this_session: u64,
202 pub tasks_this_session: u64,
203 pub providers: Vec<QuotaTrackerSnapshot>,
204}