mod types;
pub use types::*;
use std::sync::Mutex;
use std::time::{Duration, Instant};
use crate::error::TinyAgentsError;
impl RetryPolicy {
pub fn with_max_attempts(mut self, n: usize) -> Self {
self.max_attempts = n;
self
}
pub fn with_initial_backoff_ms(mut self, ms: u64) -> Self {
self.initial_backoff_ms = ms;
self
}
pub fn with_max_backoff_ms(mut self, ms: u64) -> Self {
self.max_backoff_ms = ms;
self
}
pub fn with_multiplier(mut self, m: f64) -> Self {
self.multiplier = m;
self
}
pub fn with_jitter(mut self, jitter: bool) -> Self {
self.jitter = jitter;
self
}
pub fn should_retry(&self, attempt: usize) -> bool {
attempt + 1 < self.max_attempts
}
pub fn backoff_for_attempt(&self, attempt: usize) -> Duration {
self.backoff_for_attempt_with(attempt, 0.0)
}
pub fn backoff_for_attempt_with(&self, attempt: usize, rand01: f64) -> Duration {
let base = (self.initial_backoff_ms as f64) * self.multiplier.powi(attempt as i32);
let capped = base.min(self.max_backoff_ms as f64);
let effective = if self.jitter {
capped * rand01.clamp(0.0, 1.0)
} else {
capped
};
Duration::from_millis(effective as u64)
}
}
pub fn is_retryable(err: &TinyAgentsError) -> bool {
matches!(err, TinyAgentsError::Model(_) | TinyAgentsError::Tool(_))
}
impl FallbackPolicy {
pub fn new(models: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
models: models.into_iter().map(Into::into).collect(),
}
}
pub fn next_after<'a>(&'a self, current: &str) -> Option<&'a str> {
let mut iter = self.models.iter();
while let Some(m) = iter.next() {
if m == current {
return iter.next().map(String::as_str);
}
}
None
}
}
impl RateLimiter {
pub fn new(capacity: u64, refill_per_sec: f64) -> Self {
let cap = capacity as f64;
Self {
inner: Mutex::new(types::RateLimiterState {
tokens: cap,
capacity: cap,
refill_per_sec,
last_refill: Instant::now(),
}),
}
}
pub fn try_acquire(&self, tokens: u64, now: Instant) -> bool {
let mut state = self.inner.lock().unwrap();
self.refill(&mut state, now);
if state.tokens >= tokens as f64 {
state.tokens -= tokens as f64;
true
} else {
false
}
}
pub fn available(&self, now: Instant) -> u64 {
let mut state = self.inner.lock().unwrap();
self.refill(&mut state, now);
state.tokens.floor() as u64
}
fn refill(&self, state: &mut types::RateLimiterState, now: Instant) {
let elapsed = now.duration_since(state.last_refill).as_secs_f64();
if elapsed > 0.0 {
state.tokens = (state.tokens + elapsed * state.refill_per_sec).min(state.capacity);
state.last_refill = now;
}
}
}
#[cfg(test)]
mod test;