mod anthropic;
mod common;
mod copilot;
mod deepseek;
mod kimi;
mod moonshot;
mod openai;
mod openrouter;
mod zai;
use crate::{
core::{auth, login_registry},
extensions::util::default_user_agent,
utils::http,
};
use futures_util::future::join_all;
use reqwest::Client;
use std::time::Duration;
const REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
pub(super) const SUPPORTED: &[&str] = &[
"anthropic",
"openai",
"github-copilot",
"openrouter",
"kimi-coding",
"zai",
"zai-coding-cn",
"deepseek",
"moonshotai",
"moonshotai-cn",
];
#[derive(Debug, Clone, PartialEq)]
pub struct QuotaWindow {
pub name: String,
pub percent: f32,
pub resets_at: Option<u64>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ProviderReport {
pub provider: String,
pub label: String,
pub windows: Vec<QuotaWindow>,
pub extra: Vec<(String, String)>,
pub error: Option<String>,
}
impl ProviderReport {
pub(super) fn new(provider: &str) -> Self {
Self {
provider: provider.to_string(),
label: login_registry::provider_name(provider),
..Default::default()
}
}
pub(super) fn failed(provider: &str, error: impl Into<String>) -> Self {
Self {
error: Some(error.into()),
..Self::new(provider)
}
}
}
pub(super) fn unqueried_providers() -> Vec<String> {
auth::list_configured_providers()
.into_iter()
.filter(|id| login_registry::LOGIN_PROVIDERS.iter().any(|p| p.id == id))
.filter(|id| !SUPPORTED.contains(&id.as_str()))
.collect()
}
pub(super) async fn fetch_all() -> Vec<ProviderReport> {
let client = match build_client() {
Ok(client) => client,
Err(err) => {
return vec![ProviderReport::failed("usage", err)];
}
};
let configured = auth::list_configured_providers();
let mut tasks = Vec::new();
for (index, provider) in SUPPORTED.iter().enumerate() {
if !configured.iter().any(|id| id == provider) {
continue;
}
let client = client.clone();
tasks.push(async move { (index, fetch_one(&client, provider).await) });
}
let mut out: Vec<(usize, ProviderReport)> = join_all(tasks)
.await
.into_iter()
.filter_map(|(index, report)| report.map(|report| (index, report)))
.collect();
out.sort_by_key(|(index, _)| *index);
out.into_iter().map(|(_, report)| report).collect()
}
pub(super) fn build_client() -> Result<Client, String> {
let user_agent = default_user_agent();
http::build_client_with_options(&http::ClientOptions {
timeout: REQUEST_TIMEOUT,
user_agent: Some(&user_agent),
..http::ClientOptions::default()
})
}
pub(super) async fn fetch_one(client: &Client, provider: &str) -> Option<ProviderReport> {
match provider {
"anthropic" => anthropic::fetch_anthropic(client).await,
"openai" => openai::fetch_openai(client).await,
"github-copilot" => copilot::fetch_copilot(client).await,
"openrouter" => openrouter::fetch_openrouter(client).await,
"kimi-coding" => kimi::fetch_kimi(client).await,
"zai" | "zai-coding-cn" => zai::fetch_zai(client, provider).await,
"deepseek" => deepseek::fetch_deepseek(client).await,
"moonshotai" | "moonshotai-cn" => moonshot::fetch_moonshot(client, provider).await,
_ => None,
}
}
pub(super) async fn token_for(provider: &str) -> Result<Option<String>, String> {
if let Some(token) = auth::ensure_oauth_valid(provider)
.await
.map_err(|err| err.to_string())?
{
return Ok(Some(token));
}
Ok(auth::resolve_api_key(provider, None))
}