oxios-kernel 1.19.0

Oxios kernel: supervisor, event bus, state store
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
//! Self-tracked per-provider budget (RFC-031 §4, Phase 1).
//!
//! Reuses the [`crate::budget::BudgetManager`] sliding-window+reset pattern
//! but re-keyed from `AgentId` to provider id. The cap is the subscription
//! plan allocation; the reset window is the plan's reset cadence.
//!
//! ## Why self-tracked
//!
//! ZAI / Minimax may not expose a `remaining_percent` / `resets_at` field on
//! their usage APIs. The self-tracked counter is the **primary** mechanism
//! precisely because it never depends on those fields. The
//! [`crate::token_maxing::quota_tracker::QuotaTracker`] layers
//! recalibration (where an endpoint exists) and reactive 429 cooldown
//! on top.
//!
//! ## Drift
//!
//! The counter only sees tokens oxios itself sent. If the same API key is
//! used by another app/instance, the counter under-counts. That's why
//! `QuotaTracker` recalibrates from real provider state when available,
//! and falls back to a 429 cooldown when a real request hits a hard limit.

use chrono::{DateTime, Duration, Utc};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use super::config::TokenMaxingConfig;

/// Per-provider state.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderState {
    /// Plan allocation per window.
    pub token_limit: u64,
    /// Window length (seconds).
    pub window_secs: u64,
    /// Tokens consumed in the current window.
    pub tokens_used: u64,
    /// When the current window started.
    pub window_start: DateTime<Utc>,
    /// When the window actually resets, if the provider has ever given us
    /// a real `resets_at` (via recalibration). When `Some`, it overrides
    /// the sliding-window reset.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub resets_at: Option<DateTime<Utc>>,
}

impl ProviderState {
    fn new(token_limit: u64, window_secs: u64, now: DateTime<Utc>) -> Self {
        Self {
            token_limit,
            window_secs,
            tokens_used: 0,
            window_start: now,
            resets_at: None,
        }
    }

    /// Whether the sliding window has expired.
    fn window_expired(&self, now: DateTime<Utc>) -> bool {
        now.signed_duration_since(self.window_start) >= Duration::seconds(self.window_secs as i64)
    }

    /// Reset the counter, honoring `resets_at` if present.
    fn reset(&mut self, now: DateTime<Utc>) {
        self.tokens_used = 0;
        self.window_start = now;
        if let Some(r) = self.resets_at
            && r <= now
        {
            self.resets_at = None;
        }
    }
}

/// Per-provider budget manager.
///
/// One [`ProviderBudget`] per kernel. The map is keyed by provider id (the
/// same string the engine uses, e.g. `"zai"`).
pub struct ProviderBudget {
    states: RwLock<HashMap<String, ProviderState>>,
}

impl Default for ProviderBudget {
    fn default() -> Self {
        Self::new()
    }
}

impl ProviderBudget {
    /// Create an empty budget. Use [`Self::from_config`] to populate from
    /// the user's `[token-maxing]` config.
    pub fn new() -> Self {
        Self {
            states: RwLock::new(HashMap::new()),
        }
    }

    /// Build from the user's config. Providers not eligible under the
    /// metered-never rule are silently skipped — the caller should have
    /// validated the config first.
    pub fn from_config(config: &TokenMaxingConfig) -> Self {
        let now = Utc::now();
        let mut states = HashMap::new();
        for p in &config.providers {
            if !config.is_eligible(&p.provider) {
                continue;
            }
            states.insert(
                p.provider.clone(),
                ProviderState::new(p.token_limit, p.reset_window_secs, now),
            );
        }
        Self {
            states: RwLock::new(states),
        }
    }

    /// Reload the configured providers while preserving existing usage
    /// counters. New providers start fresh at 0; removed providers are
    /// dropped; existing providers keep their `tokens_used` and window.
    pub fn reload(&self, config: &TokenMaxingConfig) {
        let now = Utc::now();
        let mut states = self.states.write();
        let eligible_ids: Vec<String> = config
            .providers
            .iter()
            .filter(|p| config.is_eligible(&p.provider))
            .map(|p| p.provider.clone())
            .collect();
        // Drop providers that disappeared from config.
        states.retain(|id, _| eligible_ids.contains(id));
        // Insert new providers.
        for p in &config.providers {
            if !config.is_eligible(&p.provider) {
                continue;
            }
            states
                .entry(p.provider.clone())
                .or_insert_with(|| ProviderState::new(p.token_limit, p.reset_window_secs, now));
        }
    }

    /// Reserve `tokens` against `provider`'s budget.
    ///
    /// `Ok(())` if the reservation fits; `Err` otherwise. The reserve
    /// here is the second line of defense — the primary gate is the
    /// availability verdict before the agent is dispatched.
    pub fn reserve(&self, provider: &str, tokens: u64) -> Result<(), ReserveError> {
        let now = Utc::now();
        let mut states = self.states.write();
        let state = states
            .get_mut(provider)
            .ok_or_else(|| ReserveError::UnknownProvider(provider.to_string()))?;

        if state.window_expired(now) {
            state.reset(now);
        }

        if state.tokens_used + tokens > state.token_limit {
            let remaining = state.token_limit.saturating_sub(state.tokens_used);
            return Err(ReserveError::Exceeded {
                limit: state.token_limit,
                used: state.tokens_used,
                requested: tokens,
                remaining,
            });
        }
        state.tokens_used += tokens;
        Ok(())
    }

    /// Release `tokens` back. Used on error/retry to free headroom.
    pub fn release(&self, provider: &str, tokens: u64) {
        let mut states = self.states.write();
        if let Some(state) = states.get_mut(provider) {
            state.tokens_used = state.tokens_used.saturating_sub(tokens);
        }
    }

    /// Read a snapshot of a provider's state, applying the reset-if-expired
    /// rule for callers that need an up-to-the-moment view.
    pub fn snapshot(&self, provider: &str) -> Option<ProviderSnapshot> {
        let now = Utc::now();
        let mut states = self.states.write();
        let state = states.get_mut(provider)?;
        if state.window_expired(now) {
            state.reset(now);
        }
        Some(ProviderSnapshot::from(&*state))
    }

    /// All provider states, snapshotted. Used by the `QuotaTracker`.
    pub fn snapshots(&self) -> Vec<(String, ProviderSnapshot)> {
        let now = Utc::now();
        let mut states = self.states.write();
        states
            .iter_mut()
            .map(|(k, v)| {
                if v.window_expired(now) {
                    v.reset(now);
                }
                (k.clone(), ProviderSnapshot::from(&*v))
            })
            .collect()
    }

    /// Snap a state's `tokens_used` to a real value, then close the
    /// window. Called by `QuotaTracker` when a fetcher returns
    /// `remaining_percent` / `resets_at`.
    ///
    /// `used_after_calibration` is what the counter should show right
    /// after the snap (i.e. `token_limit - remaining`).
    pub fn recalibrate(
        &self,
        provider: &str,
        used_after_calibration: u64,
        resets_at: Option<DateTime<Utc>>,
    ) -> bool {
        let now = Utc::now();
        let mut states = self.states.write();
        if let Some(state) = states.get_mut(provider) {
            state.tokens_used = used_after_calibration.min(state.token_limit);
            state.window_start = now;
            state.resets_at = resets_at;
            true
        } else {
            false
        }
    }
}
#[derive(Debug, Clone, PartialEq, Serialize)]
/// Read-only view of a [`ProviderState`] at a moment in time.
pub struct ProviderSnapshot {
    pub token_limit: u64,
    pub window_secs: u64,
    pub tokens_used: u64,
    pub tokens_remaining: u64,
    pub remaining_percent: f64,
    pub window_start: DateTime<Utc>,
    pub resets_at: Option<DateTime<Utc>>,
    /// Seconds until the window resets. Honours `resets_at` when present.
    pub window_remaining_secs: u64,
}

impl From<&ProviderState> for ProviderSnapshot {
    fn from(s: &ProviderState) -> Self {
        let tokens_remaining = s.token_limit.saturating_sub(s.tokens_used);
        let remaining_percent = if s.token_limit == 0 {
            0.0
        } else {
            (tokens_remaining as f64 / s.token_limit as f64) * 100.0
        };
        let window_remaining_secs = match s.resets_at {
            Some(r) => (r - Utc::now()).num_seconds().max(0) as u64,
            None => {
                let elapsed = (Utc::now() - s.window_start).num_seconds().max(0) as u64;
                s.window_secs.saturating_sub(elapsed)
            }
        };
        Self {
            token_limit: s.token_limit,
            window_secs: s.window_secs,
            tokens_used: s.tokens_used,
            tokens_remaining,
            remaining_percent,
            window_start: s.window_start,
            resets_at: s.resets_at,
            window_remaining_secs,
        }
    }
}

impl ProviderSnapshot {
    /// Build a snapshot directly from parts — used by the v2 live
    /// quota path. `window_secs` defaults to 1h as a placeholder
    /// when no live `resets_at` is known.
    pub fn from_parts(
        _provider: &str,
        tokens_used: u64,
        token_limit: u64,
        resets_at: Option<DateTime<Utc>>,
        _floor_percent: u8,
    ) -> Self {
        let tokens_remaining = token_limit.saturating_sub(tokens_used);
        let remaining_percent = if token_limit == 0 {
            0.0
        } else {
            (tokens_remaining as f64 / token_limit as f64) * 100.0
        };
        let now = Utc::now();
        let window_remaining_secs = match resets_at {
            Some(r) => (r - now).num_seconds().max(0) as u64,
            None => 3600,
        };
        Self {
            token_limit,
            window_secs: 3600,
            tokens_used,
            tokens_remaining,
            remaining_percent,
            window_start: now,
            resets_at,
            window_remaining_secs,
        }
    }
}

/// Errors from [`ProviderBudget::reserve`].
#[derive(Debug, Clone, thiserror::Error)]
pub enum ReserveError {
    /// Provider has no budget configured (ineligible).
    #[error("no budget configured for provider '{0}'")]
    UnknownProvider(String),
    /// Requested tokens would push the counter past the plan limit.
    #[error(
        "token limit exceeded: requested {requested} but only {remaining} remaining (limit {limit})"
    )]
    Exceeded {
        limit: u64,
        used: u64,
        requested: u64,
        /// Convenience field — equal to `limit - used` at error time.
        /// thiserror can format it directly without a helper.
        remaining: u64,
    },
}

impl ReserveError {
    /// Tokens still available in the current window. `0` for
    /// `UnknownProvider`.
    pub fn remaining(&self) -> u64 {
        match self {
            ReserveError::UnknownProvider(_) => 0,
            ReserveError::Exceeded { remaining, .. } => *remaining,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::super::config::TokenMaxingProviderConfig;
    use super::*;

    fn cfg_with(provider: &str, limit: u64, window: u64) -> TokenMaxingConfig {
        TokenMaxingConfig {
            enabled: true,
            providers: vec![TokenMaxingProviderConfig {
                provider: provider.into(),
                billing_model: "subscription".into(),
                token_limit: limit,
                reset_window_secs: window,
                min_remaining_percent: None,
                models: vec![],
            }],
            default_min_remaining_percent: 5,
            recalibration_interval_secs: 0,
            parallel_providers: false,
        }
    }

    #[test]
    fn reserve_within_budget_succeeds() {
        let b = ProviderBudget::from_config(&cfg_with("zai", 1000, 3600));
        assert!(b.reserve("zai", 500).is_ok());
        let s = b.snapshot("zai").unwrap();
        assert_eq!(s.tokens_used, 500);
        assert_eq!(s.tokens_remaining, 500);
    }

    #[test]
    fn reserve_exceeds_budget() {
        let b = ProviderBudget::from_config(&cfg_with("zai", 1000, 3600));
        b.reserve("zai", 1000).unwrap();
        let err = b.reserve("zai", 1).unwrap_err();
        match err {
            ReserveError::Exceeded {
                limit,
                used,
                requested,
                ..
            } => {
                assert_eq!(limit, 1000);
                assert_eq!(used, 1000);
                assert_eq!(requested, 1);
            }
            _ => panic!("wrong variant"),
        }
    }

    #[test]
    fn unknown_provider_rejected() {
        let b = ProviderBudget::from_config(&cfg_with("zai", 1000, 3600));
        assert!(matches!(
            b.reserve("anthropic", 1).unwrap_err(),
            ReserveError::UnknownProvider(_)
        ));
    }

    #[test]
    fn release_returns_tokens() {
        let b = ProviderBudget::from_config(&cfg_with("zai", 1000, 3600));
        b.reserve("zai", 800).unwrap();
        b.release("zai", 300);
        let s = b.snapshot("zai").unwrap();
        assert_eq!(s.tokens_used, 500);
    }

    #[test]
    fn recalibrate_snaps_counter() {
        let b = ProviderBudget::from_config(&cfg_with("zai", 1000, 3600));
        b.reserve("zai", 700).unwrap();
        let r = b.recalibrate("zai", 850, None);
        assert!(r);
        let s = b.snapshot("zai").unwrap();
        assert_eq!(s.tokens_used, 850);
    }

    #[test]
    fn reload_preserves_existing_usage() {
        let b = ProviderBudget::from_config(&cfg_with("zai", 1000, 3600));
        b.reserve("zai", 400).unwrap();
        b.reload(&cfg_with("zai", 1000, 3600));
        let s = b.snapshot("zai").unwrap();
        assert_eq!(s.tokens_used, 400);
    }

    #[test]
    fn reload_drops_removed_providers() {
        let b = ProviderBudget::from_config(&cfg_with("zai", 1000, 3600));
        let new_cfg = TokenMaxingConfig {
            enabled: true,
            ..TokenMaxingConfig::default()
        };
        b.reload(&new_cfg);
        assert!(b.snapshot("zai").is_none());
    }

    #[test]
    fn eligibility_filter_blocks_metered() {
        let mut cfg = cfg_with("openai", 1000, 3600);
        cfg.providers[0].billing_model = "metered".into();
        let b = ProviderBudget::from_config(&cfg);
        assert!(b.snapshot("openai").is_none());
        assert!(matches!(
            b.reserve("openai", 1).unwrap_err(),
            ReserveError::UnknownProvider(_)
        ));
    }

    #[test]
    fn remaining_percent_computed() {
        let b = ProviderBudget::from_config(&cfg_with("zai", 1000, 3600));
        b.reserve("zai", 250).unwrap();
        let s = b.snapshot("zai").unwrap();
        assert!((s.remaining_percent - 75.0).abs() < 1e-9);
    }
}