use crate::types::{BudgetScope, ProviderId};
use async_trait::async_trait;
use thiserror::Error;
pub struct Permit {
on_drop: Option<Box<dyn FnOnce() + Send>>,
}
impl Permit {
pub fn new<F>(on_drop: F) -> Self
where
F: FnOnce() + Send + 'static,
{
Self {
on_drop: Some(Box::new(on_drop)),
}
}
}
impl Drop for Permit {
fn drop(&mut self) {
if let Some(cb) = self.on_drop.take() {
cb();
}
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct BudgetSnapshot {
pub remaining: i64,
pub limit: i64,
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum BudgetLimit {
Rps(u32),
}
pub type Host = String;
#[async_trait]
pub trait Governor: Send + Sync {
async fn acquire_llm(
&self,
provider: ProviderId,
est_tokens: u32,
) -> Result<Permit, GovernorError>;
async fn acquire_egress(&self, host: Host) -> Result<Permit, GovernorError>;
async fn budget(&self, scope: BudgetScope) -> BudgetSnapshot;
async fn fence(&self, scope: BudgetScope, limit: BudgetLimit) -> Result<(), GovernorError>;
async fn acquire_llm_scoped(
&self,
scope: BudgetScope,
est_tokens: u32,
) -> Result<Permit, GovernorError> {
match scope {
BudgetScope::Provider(p) => self.acquire_llm(p, est_tokens).await,
BudgetScope::TenantProvider(_, p) => self.acquire_llm(p, est_tokens).await,
other => Err(GovernorError::Unavailable(format!(
"acquire_llm_scoped requires a provider-bearing scope, got {other}"
))),
}
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum GovernorError {
#[error("saturated: {scope}")]
Saturated {
scope: String,
},
#[error("acquire timed out after {millis}ms")]
TimedOut {
millis: u64,
},
#[error("unavailable: {0}")]
Unavailable(String),
}