use serde::{Deserialize, Serialize};
pub const SUBSCRIPTION_BILLING_MODEL: &str = "subscription";
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TokenMaxingConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub providers: Vec<TokenMaxingProviderConfig>,
#[serde(default = "default_min_remaining_percent")]
pub default_min_remaining_percent: u8,
#[serde(default = "default_recalibration_interval_secs")]
pub recalibration_interval_secs: u64,
#[serde(default = "default_parallel_providers")]
pub parallel_providers: bool,
}
fn default_min_remaining_percent() -> u8 {
5
}
fn default_recalibration_interval_secs() -> u64 {
60
}
fn default_parallel_providers() -> bool {
false
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenMaxingProviderConfig {
pub provider: String,
pub billing_model: String,
pub token_limit: u64,
pub reset_window_secs: u64,
#[serde(default)]
pub min_remaining_percent: Option<u8>,
#[serde(default)]
pub models: Vec<String>,
}
impl TokenMaxingProviderConfig {
pub fn min_remaining_percent(&self, default: u8) -> u8 {
self.min_remaining_percent.unwrap_or(default)
}
}
impl TokenMaxingConfig {
pub fn get(&self, provider: &str) -> Option<&TokenMaxingProviderConfig> {
self.providers.iter().find(|p| p.provider == provider)
}
pub fn is_eligible(&self, provider: &str) -> bool {
match self.get(provider) {
Some(p) => {
p.billing_model == SUBSCRIPTION_BILLING_MODEL
&& p.token_limit > 0
&& p.reset_window_secs > 0
}
None => false,
}
}
pub fn validate(&self) -> Vec<String> {
let mut errors = Vec::new();
if self.enabled && self.providers.is_empty() {
errors.push(
"token-maxing.enabled = true but no providers configured — refuse to start"
.to_string(),
);
}
for (i, p) in self.providers.iter().enumerate() {
if p.provider.trim().is_empty() {
errors.push(format!("token-maxing.providers[{i}].provider is empty"));
}
if p.billing_model != SUBSCRIPTION_BILLING_MODEL {
errors.push(format!(
"token-maxing.providers[{i}].billing_model = {:?} is not permitted; the only accepted value is {:?}",
p.billing_model, SUBSCRIPTION_BILLING_MODEL,
));
}
if p.token_limit == 0 {
errors.push(format!(
"token-maxing.providers[{i}].token_limit must be > 0"
));
}
if p.reset_window_secs == 0 {
errors.push(format!(
"token-maxing.providers[{i}].reset_window_secs must be > 0"
));
}
}
errors
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subscription_only_rule() {
let cfg = TokenMaxingConfig {
enabled: true,
providers: vec![TokenMaxingProviderConfig {
provider: "openai".into(),
billing_model: "metered".into(),
token_limit: 1_000_000,
reset_window_secs: 3600,
min_remaining_percent: None,
models: vec![],
}],
default_min_remaining_percent: 5,
recalibration_interval_secs: 60,
parallel_providers: false,
};
assert!(!cfg.is_eligible("openai"));
assert!(!cfg.is_eligible("zai"));
assert!(!cfg.validate().is_empty());
}
#[test]
fn subscription_passes() {
let cfg = TokenMaxingConfig {
enabled: true,
providers: vec![TokenMaxingProviderConfig {
provider: "zai".into(),
billing_model: SUBSCRIPTION_BILLING_MODEL.into(),
token_limit: 2_000_000,
reset_window_secs: 18000,
min_remaining_percent: Some(7),
models: vec!["zai/glm-4.6".into()],
}],
default_min_remaining_percent: 5,
recalibration_interval_secs: 60,
parallel_providers: false,
};
assert!(cfg.is_eligible("zai"));
assert!(!cfg.is_eligible("anthropic"));
assert!(cfg.validate().is_empty());
let p = cfg.get("zai").unwrap();
assert_eq!(p.min_remaining_percent(5), 7);
}
#[test]
fn empty_when_disabled_means_no_providers() {
let cfg = TokenMaxingConfig::default();
assert!(!cfg.enabled);
assert!(!cfg.is_eligible("anything"));
}
}