use std::collections::HashMap;
use crate::utils::http::get_user_agent;
#[derive(Debug, Clone, PartialEq, serde::Deserialize, Default)]
pub struct RateLimit {
pub utilization: Option<f64>,
#[serde(rename = "resets_at")]
pub resets_at: Option<String>,
}
#[derive(Debug, Clone, serde::Deserialize, Default)]
pub struct ExtraUsage {
#[serde(rename = "is_enabled")]
pub is_enabled: bool,
#[serde(rename = "monthly_limit")]
pub monthly_limit: Option<i64>,
#[serde(rename = "used_credits")]
pub used_credits: Option<i64>,
pub utilization: Option<f64>,
}
#[derive(Debug, Clone, serde::Deserialize, Default)]
pub struct Utilization {
#[serde(rename = "five_hour")]
pub five_hour: Option<RateLimit>,
#[serde(rename = "seven_day")]
pub seven_day: Option<RateLimit>,
#[serde(rename = "seven_day_oauth_apps")]
pub seven_day_oauth_apps: Option<RateLimit>,
#[serde(rename = "seven_day_opus")]
pub seven_day_opus: Option<RateLimit>,
#[serde(rename = "seven_day_sonnet")]
pub seven_day_sonnet: Option<RateLimit>,
#[serde(rename = "extra_usage")]
pub extra_usage: Option<ExtraUsage>,
}
fn is_claude_ai_subscriber() -> bool {
std::env::var("AI_CODE_OAUTH_TOKEN").is_ok()
}
fn has_profile_scope() -> bool {
std::env::var("AI_CODE_OAUTH_TOKEN").is_ok()
}
fn get_oauth_config() -> OauthConfig {
OauthConfig {
base_api_url: std::env::var("AI_CODE_API_URL")
.unwrap_or_else(|_| "https://api.anthropic.com".to_string()),
}
}
#[derive(Debug, Clone)]
pub struct OauthConfig {
pub base_api_url: String,
}
fn get_auth_headers() -> HashMap<String, String> {
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
headers.insert("anthropic-version".to_string(), "2023-06-01".to_string());
headers.insert("User-Agent".to_string(), get_user_agent());
if let Ok(token) = std::env::var("AI_CODE_OAUTH_TOKEN") {
headers.insert("Authorization".to_string(), format!("Bearer {}", token));
}
headers
}
fn is_oauth_token_expired(expires_at: &str) -> bool {
if expires_at.is_empty() {
return false;
}
false
}
fn get_oauth_token() -> Option<String> {
std::env::var("AI_CODE_OAUTH_TOKEN").ok()
}
pub async fn fetch_utilization() -> Result<Utilization, String> {
if !is_claude_ai_subscriber() || !has_profile_scope() {
return Ok(Utilization::default());
}
if let Some(token) = get_oauth_token() {
}
let config = get_oauth_config();
let endpoint = format!("{}/api/oauth/organizations/me/usage", config.base_api_url);
let headers = get_auth_headers();
let reqwest_headers: reqwest::header::HeaderMap = headers
.into_iter()
.filter_map(|(k, v)| {
let key: reqwest::header::HeaderName = k.parse().ok()?;
let value: reqwest::header::HeaderValue = v.parse().ok()?;
Some((key, value))
})
.collect();
let client = reqwest::Client::new();
let response = client
.get(&endpoint)
.headers(reqwest_headers)
.send()
.await
.map_err(|e| e.to_string())?;
if response.status() == reqwest::StatusCode::NO_CONTENT {
return Ok(Utilization::default());
}
if !response.status().is_success() {
return Err(format!("API returned status: {}", response.status()));
}
response
.json::<Utilization>()
.await
.map_err(|e| e.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rate_limit_default() {
let rate_limit = RateLimit::default();
assert_eq!(rate_limit.utilization, None);
assert_eq!(rate_limit.resets_at, None);
}
#[test]
fn test_extra_usage_default() {
let extra_usage = ExtraUsage::default();
assert!(!extra_usage.is_enabled);
assert_eq!(extra_usage.monthly_limit, None);
}
#[test]
fn test_utilization_default() {
let utilization = Utilization::default();
assert_eq!(utilization.five_hour, None);
assert_eq!(utilization.seven_day, None);
}
}