use crate::paths::aid_dir;
use crate::route_availability::{
Hold, HoldEnd, MANUAL_HOLD, RouteAvailability, RouteStatus, StoredHold, availability,
availability_for_group, availability_for_model, classify_hold, format_hold_end_for,
stored_hold,
};
use crate::types::AgentKind;
use chrono::{DateTime, Local, NaiveDateTime};
#[cfg(test)]
use chrono::{Datelike, Timelike};
use std::fs;
use std::path::PathBuf;
#[path = "rate_limit_claude.rs"]
mod claude;
pub(crate) use claude::{claude_rejected_limit, parse_claude_reset_clock, parse_recovery_time};
#[path = "rate_limit_markers.rs"]
mod markers;
pub use markers::*;
#[path = "rate_limit_state.rs"]
mod state;
use state::discovered_group_markers;
pub use state::*;
#[path = "rate_limit_detection.rs"]
mod detection;
use detection::parse_iso_recovery_time;
#[cfg(test)]
pub(crate) use detection::test_future_recovery_time;
pub use detection::*;
pub(crate) use detection::{parse_recovery_datetime, refusal_on_channel};
#[path = "rate_limit_info.rs"]
mod info;
pub use info::*;
pub(crate) use markers::{format_recovery, group_marker_path, marker_field, marker_path};
pub(crate) const RATE_LIMIT_WINDOW_SECS: u64 = 300;
#[cfg(test)]
fn assert_marker_path_isolated() {
crate::paths::assert_aid_home_isolated("rate_limit::marker_path");
}
pub fn resolve_agent(name: &str) -> (AgentKind, Option<&str>) {
match AgentKind::parse_str(name) {
Some(kind) => (kind, None),
None => (AgentKind::Custom, Some(name)),
}
}
pub fn dispatch_blocking_hold(agent: &AgentKind, custom_name: Option<&str>) -> Option<String> {
hold_text(agent, custom_name, &availability(agent, custom_name))
}
pub fn dispatch_blocking_hold_for_model(
agent: &AgentKind,
custom_name: Option<&str>,
model: Option<&str>,
) -> Option<String> {
hold_text(
agent,
custom_name,
&availability_for_model(agent, custom_name, model),
)
}
fn hold_text(
agent: &AgentKind,
custom_name: Option<&str>,
avail: &RouteAvailability,
) -> Option<String> {
match avail.status {
RouteStatus::Held => Some(dispatch_hold_end(agent, custom_name, avail)),
_ => None,
}
}
fn dispatch_hold_end(
agent: &AgentKind,
custom_name: Option<&str>,
avail: &RouteAvailability,
) -> String {
match &avail.ends {
HoldEnd::At(_) => avail
.why
.strip_prefix("held ")
.unwrap_or(&avail.why)
.to_string(),
HoldEnd::ClearLimit { slug } => {
format!("until cleared with `aid config clear-limit {slug}`")
}
HoldEnd::SnapshotDatedWindow => {
format_hold_end_for(&StoredHold::Windowed, agent, custom_name, None)
}
HoldEnd::Cooldown | HoldEnd::Nothing => avail.why.clone(),
}
}
#[cfg(test)]
#[path = "rate_limit_credibility_tests.rs"]
mod credibility_tests;
#[cfg(test)]
#[path = "rate_limit_recovery_tests.rs"]
mod recovery_tests;
#[cfg(test)]
#[path = "rate_limit_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "rate_limit_stale_tests.rs"]
mod stale_clear_tests;
#[cfg(test)]
#[path = "rate_limit_hold_tests.rs"]
mod hold_tests;
#[cfg(test)]
#[path = "rate_limit_custom_tests.rs"]
mod custom_tests;
#[cfg(test)]
mod home_guard_tests {
use super::*;
use crate::paths::{self, AidHomeGuard};
#[test]
fn marker_path_writes_under_isolated_home() {
let temp = tempfile::tempdir().unwrap();
let _guard = AidHomeGuard::set(temp.path());
std::fs::create_dir_all(paths::aid_dir()).unwrap();
mark_rate_limited(&AgentKind::Codex, None, "rate limit exceeded");
let marker = paths::aid_dir().join("rate-limit-codex");
assert!(marker.exists());
assert!(marker.starts_with(temp.path()));
}
#[test]
fn marker_path_refuses_real_home_without_guard() {
let resolved = paths::aid_dir();
let home = std::env::var("HOME")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| std::path::PathBuf::from("."));
let real = home.join(".aid");
if resolved != real {
return;
}
let err = std::panic::catch_unwind(|| {
let _ = marker_path(&AgentKind::Codex, None);
});
assert!(
err.is_err(),
"marker_path must refuse real ~/.aid without AidHomeGuard"
);
}
}