From edbf2defbbac98d1b7d118230b463a861a9a8c05 Mon Sep 17 00:00:00 2001 From: RCD <90105158+Finesssee@users.noreply.github.com> Date: Wed, 30 Sep 2026 14:25:26 +0700 Subject: [PATCH 1/3] Port upstream 0.64.0: LLM Proxy base URL setting, private-network HTTP, tolerant quota_groups --- .../src/commands/provider_settings.rs | 1 + apps/desktop-tauri/src/i18n/keys.ts | 4 + .../sections/CredentialsDispatcher.tsx | 1 + .../sections/credentials/OpenAiExtras.tsx | 8 + rust/src/locale.rs | 4 + rust/src/locale/en-US.ftl | 4 + rust/src/providers/llmproxy/mod.rs | 221 ++++++++++++++++-- rust/src/providers/mod.rs | 49 +++- rust/src/settings/api_keys.rs | 4 +- rust/src/settings/provider_workspace.rs | 35 +++ 10 files changed, 309 insertions(+), 22 deletions(-) diff --git a/apps/desktop-tauri/src-tauri/src/commands/provider_settings.rs b/apps/desktop-tauri/src-tauri/src/commands/provider_settings.rs index f9344fcfba..7ad055806c 100644 --- a/apps/desktop-tauri/src-tauri/src/commands/provider_settings.rs +++ b/apps/desktop-tauri/src-tauri/src/commands/provider_settings.rs @@ -326,6 +326,7 @@ fn workspace_provider(provider_id: &str) -> Option { "devin" => ProviderId::Devin, "opencodego" => ProviderId::OpenCodeGo, "zed" => ProviderId::Zed, + "llmproxy" => ProviderId::LLMProxy, "xai" => ProviderId::Xai, "v0" => ProviderId::V0, "helmcode" => ProviderId::Helmcode, diff --git a/apps/desktop-tauri/src/i18n/keys.ts b/apps/desktop-tauri/src/i18n/keys.ts index 195620e3bf..cc6d646a65 100644 --- a/apps/desktop-tauri/src/i18n/keys.ts +++ b/apps/desktop-tauri/src/i18n/keys.ts @@ -887,6 +887,10 @@ export const ALL_LOCALE_KEYS = [ "Sub2ApiBaseUrlLabel", "Sub2ApiBaseUrlPlaceholder", "Sub2ApiBaseUrlHelp", + "LlmProxyTitle", + "LlmProxyBaseUrlLabel", + "LlmProxyBaseUrlPlaceholder", + "LlmProxyBaseUrlHelp", // Tray icon visibility (Windows 11 hidden-icons overflow) "PromoteTrayIconLabel", diff --git a/apps/desktop-tauri/src/surfaces/settings/providers/sections/CredentialsDispatcher.tsx b/apps/desktop-tauri/src/surfaces/settings/providers/sections/CredentialsDispatcher.tsx index 76c2652ecd..c4594bb282 100644 --- a/apps/desktop-tauri/src/surfaces/settings/providers/sections/CredentialsDispatcher.tsx +++ b/apps/desktop-tauri/src/surfaces/settings/providers/sections/CredentialsDispatcher.tsx @@ -39,6 +39,7 @@ export function CredentialsDispatcher({ providerId, t }: Props) { case "opencodego": case "zed": case "sub2api": + case "llmproxy": case "xai": return ; case "openrouter": diff --git a/apps/desktop-tauri/src/surfaces/settings/providers/sections/credentials/OpenAiExtras.tsx b/apps/desktop-tauri/src/surfaces/settings/providers/sections/credentials/OpenAiExtras.tsx index 3ed6842890..a10b8277bc 100644 --- a/apps/desktop-tauri/src/surfaces/settings/providers/sections/credentials/OpenAiExtras.tsx +++ b/apps/desktop-tauri/src/surfaces/settings/providers/sections/credentials/OpenAiExtras.tsx @@ -114,6 +114,7 @@ const WORKSPACE_EXTRA_IDS: Record = { opencodego: true, zed: true, sub2api: true, + llmproxy: true, xai: true, fireworks: true, }; @@ -162,6 +163,13 @@ function extraConfig(providerId: string, t: Props["t"]) { placeholder: t("Sub2ApiBaseUrlPlaceholder"), help: t("Sub2ApiBaseUrlHelp"), }; + case "llmproxy": + return { + title: t("LlmProxyTitle"), + label: t("LlmProxyBaseUrlLabel"), + placeholder: t("LlmProxyBaseUrlPlaceholder"), + help: t("LlmProxyBaseUrlHelp"), + }; case "xai": return { title: "xAI team", diff --git a/rust/src/locale.rs b/rust/src/locale.rs index e6ec918ac4..fef648af18 100644 --- a/rust/src/locale.rs +++ b/rust/src/locale.rs @@ -1167,6 +1167,10 @@ locale_keys! { Sub2ApiBaseUrlLabel, Sub2ApiBaseUrlPlaceholder, Sub2ApiBaseUrlHelp, + LlmProxyTitle, + LlmProxyBaseUrlLabel, + LlmProxyBaseUrlPlaceholder, + LlmProxyBaseUrlHelp, // Tray icon visibility (Windows 11 hidden-icons overflow) PromoteTrayIconLabel, diff --git a/rust/src/locale/en-US.ftl b/rust/src/locale/en-US.ftl index fb96ecd4b8..039efd6f7e 100644 --- a/rust/src/locale/en-US.ftl +++ b/rust/src/locale/en-US.ftl @@ -797,6 +797,10 @@ Sub2ApiTitle = sub2api Sub2ApiBaseUrlLabel = Base URL Sub2ApiBaseUrlPlaceholder = https://sub2api.example.com Sub2ApiBaseUrlHelp = Deployment URL for GET /v1/usage. Use HTTPS, or loopback HTTP (127.0.0.1 / localhost) for local development. +LlmProxyTitle = LLM Proxy +LlmProxyBaseUrlLabel = Base URL +LlmProxyBaseUrlPlaceholder = https://proxy.example.com +LlmProxyBaseUrlHelp = Base URL for the LLM-API-Key-Proxy instance, used for /v1/quota-stats. Use HTTPS, or plain HTTP for loopback, private-network and .local hosts. LLM_PROXY_BASE_URL is the fallback. PromoteTrayIconLabel = Pin to Taskbar (Windows 11) PromoteTrayIconHelper = Always show the CodexBar icon on the taskbar instead of inside the overflow chevron PromoteTrayIconUnsupportedHint = Not available on this operating system. Drag the icon out of the ^ overflow area in Windows Settings > Taskbar to pin it manually. diff --git a/rust/src/providers/llmproxy/mod.rs b/rust/src/providers/llmproxy/mod.rs index f820cbc4b4..5aaaca339c 100644 --- a/rust/src/providers/llmproxy/mod.rs +++ b/rust/src/providers/llmproxy/mod.rs @@ -14,6 +14,7 @@ use crate::core::{ }; const LLM_PROXY_CREDENTIAL_TARGET: &str = "codexbar-llmproxy"; +const LLM_PROXY_BASE_URL_ENV: &str = "LLM_PROXY_BASE_URL"; #[derive(Debug, Deserialize)] struct QuotaStatsResponse { @@ -30,6 +31,7 @@ struct ProviderStats { tokens: Option, #[serde(rename = "approx_cost")] approximate_cost: Option, + #[serde(default, deserialize_with = "lenient_quota_groups")] quota_groups: Option, } @@ -55,6 +57,16 @@ enum QuotaGroups { Map(HashMap), } +/// Upstream treats malformed `quota_groups` as absent without discarding the +/// rest of the provider's usage (`llmproxy.ts`, native decoding note). +fn lenient_quota_groups<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let value = serde_json::Value::deserialize(deserializer)?; + Ok(serde_json::from_value(value).ok().flatten()) +} + #[derive(Debug, Clone, Deserialize)] struct QuotaGroup { remaining_percent: Option, @@ -118,7 +130,7 @@ impl LLMProxyProvider { ) -> Result { let response = self .client - .get(quota_stats_url(base_url)?) + .get(quota_stats_url(base_url)) .bearer_auth(api_key) .header("Accept", "application/json") .send() @@ -172,7 +184,7 @@ impl Provider for LLMProxyProvider { LLM_PROXY_CREDENTIAL_TARGET, &["LLM_PROXY_API_KEY"], )?; - let base_url = resolve_base_url()?; + let base_url = resolve_base_url(ctx)?; self.fetch_api(&api_key, base_url).await } SourceMode::Web | SourceMode::Cli => { @@ -186,27 +198,69 @@ impl Provider for LLMProxyProvider { } } -fn resolve_base_url() -> Result { - let raw = std::env::var("LLM_PROXY_BASE_URL").map_err(|_| { - ProviderError::NotInstalled( - "LLM Proxy base URL not found. Set LLM_PROXY_BASE_URL in the environment.".to_string(), - ) - })?; - crate::providers::validated_https_url(&raw, "LLM Proxy") +/// The Settings value wins; `LLM_PROXY_BASE_URL` is the fallback. +fn resolve_base_url(ctx: &FetchContext) -> Result { + let raw = ctx + .workspace_id + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(str::to_string) + .or_else(|| { + std::env::var(LLM_PROXY_BASE_URL_ENV) + .ok() + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) + }) + .ok_or_else(|| { + ProviderError::NotInstalled(format!( + "LLM Proxy base URL not found. Add one in Settings or set {LLM_PROXY_BASE_URL_ENV}." + )) + })?; + crate::providers::validated_https_or_private_http_url(&raw, "LLM Proxy") } -fn quota_stats_url(base_url: Url) -> Result { - let path = base_url.path().trim_matches('/'); - let versioned = if path.split('/').next_back() == Some("v1") { - base_url +/// Build `{base}/v1/quota-stats`, keeping any query/fragment at the end. +/// +/// `/v1` is appended only when the percent-decoded path does not already end +/// in `/v1` (upstream `llmproxy.ts`). +fn quota_stats_url(mut base_url: Url) -> Url { + let query = base_url.query().map(str::to_owned); + let fragment = base_url.fragment().map(str::to_owned); + base_url.set_query(None); + base_url.set_fragment(None); + + let path = base_url.path().trim_end_matches('/').to_string(); + let version = if percent_decode(&path).ends_with("/v1") { + "" } else { - base_url - .join("v1/") - .map_err(|e| ProviderError::Other(format!("Invalid LLM Proxy URL: {e}")))? + "/v1" }; - versioned - .join("quota-stats") - .map_err(|e| ProviderError::Other(format!("Invalid LLM Proxy quota-stats URL: {e}"))) + base_url.set_path(&format!("{path}{version}/quota-stats")); + base_url.set_query(query.as_deref()); + base_url.set_fragment(fragment.as_deref()); + base_url +} + +fn percent_decode(input: &str) -> String { + let bytes = input.as_bytes(); + let mut out = Vec::with_capacity(bytes.len()); + let mut idx = 0; + while idx < bytes.len() { + let decoded = (bytes[idx] == b'%') + .then(|| bytes.get(idx + 1..idx + 3)) + .flatten() + .and_then(|hex| std::str::from_utf8(hex).ok()) + .and_then(|hex| u8::from_str_radix(hex, 16).ok()); + if let Some(byte) = decoded { + out.push(byte); + idx += 3; + } else { + out.push(bytes[idx]); + idx += 1; + } + } + String::from_utf8_lossy(&out).into_owned() } fn parse_summary(data: &[u8]) -> Result { @@ -470,6 +524,135 @@ mod tests { assert_eq!(snapshot.extra_rate_windows.len(), 2); } + fn stats_url(raw: &str) -> String { + let base = crate::providers::validated_https_or_private_http_url(raw, "LLM Proxy").unwrap(); + quota_stats_url(base).to_string() + } + + #[test] + fn quota_stats_url_appends_v1_only_when_missing() { + assert_eq!( + stats_url("https://proxy.example.com"), + "https://proxy.example.com/v1/quota-stats" + ); + assert_eq!( + stats_url("https://proxy.example.com/"), + "https://proxy.example.com/v1/quota-stats" + ); + assert_eq!( + stats_url("https://proxy.example.com/v1"), + "https://proxy.example.com/v1/quota-stats" + ); + assert_eq!( + stats_url("https://proxy.example.com/v1//"), + "https://proxy.example.com/v1/quota-stats" + ); + assert_eq!( + stats_url("https://proxy.example.com/gateway"), + "https://proxy.example.com/gateway/v1/quota-stats" + ); + // Percent-decoded path already ends in /v1. + assert_eq!( + stats_url("https://proxy.example.com/%76%31"), + "https://proxy.example.com/%76%31/quota-stats" + ); + } + + #[test] + fn quota_stats_url_keeps_query_and_fragment_at_the_end() { + assert_eq!( + stats_url("https://proxy.example.com?team=a"), + "https://proxy.example.com/v1/quota-stats?team=a" + ); + assert_eq!( + stats_url("https://proxy.example.com/v1/?team=a#frag"), + "https://proxy.example.com/v1/quota-stats?team=a#frag" + ); + assert_eq!( + stats_url("https://proxy.example.com/gw#frag"), + "https://proxy.example.com/gw/v1/quota-stats#frag" + ); + } + + #[test] + fn base_url_policy_allows_https_and_private_network_http_only() { + for ok in [ + "https://proxy.example.com", + "http://127.0.0.1:8000", + "http://localhost:8000", + "http://[::1]:8000", + "http://10.0.0.5", + "http://172.16.4.2", + "http://192.168.1.10:8000", + "http://169.254.1.1", + "http://proxy.local", + "http://[fd00::1]", + ] { + assert!( + crate::providers::validated_https_or_private_http_url(ok, "LLM Proxy").is_ok(), + "rejected {ok}" + ); + } + for bad in [ + "http://proxy.example.com", + "http://172.32.0.1", + "http://8.8.8.8", + "ftp://proxy.local", + "https://user:pass@proxy.example.com", + "http://user@192.168.1.10", + "", + ] { + assert!( + crate::providers::validated_https_or_private_http_url(bad, "LLM Proxy").is_err(), + "accepted {bad}" + ); + } + } + + #[test] + fn settings_base_url_wins_over_env() { + let ctx = FetchContext { + workspace_id: Some(" http://192.168.1.10:8000 ".into()), + ..FetchContext::default() + }; + assert_eq!( + resolve_base_url(&ctx).unwrap().as_str(), + "http://192.168.1.10:8000/" + ); + } + + #[test] + fn malformed_quota_groups_are_absent_without_dropping_usage() { + let summary = parse_summary( + br#"{ + "providers": { + "a": {"total_requests": 5, "quota_groups": "nope"}, + "b": {"total_requests": 7, "quota_groups": null}, + "c": {"total_requests": 11, "quota_groups": [1, 2]}, + "d": {"total_requests": 13, "quota_groups": {"x": {"remaining_percent": "bad"}}}, + "e": {"total_requests": 17, "quota_groups": [{"remaining_percent": 40.0}]} + } + }"#, + ) + .unwrap(); + assert_eq!(summary.total_requests, 53); + assert_eq!(summary.minimum_remaining_percent, Some(40.0)); + } + + #[test] + fn accepts_array_and_object_quota_groups() { + let summary = parse_summary( + br#"{ + "providers": { + "a": {"quota_groups": [{"remaining_percent": 30.0}]}, + "b": {"quota_groups": {"k": {"remaining_percent": 20.0}}} + } + }"#, + ) + .unwrap(); + assert_eq!(summary.minimum_remaining_percent, Some(20.0)); + } + #[test] fn next_reset_at_ignores_elapsed_resets() { let now = DateTime::parse_from_rfc3339("2026-05-15T12:00:00Z") diff --git a/rust/src/providers/mod.rs b/rust/src/providers/mod.rs index de9d265f6e..2b48e87f4b 100755 --- a/rust/src/providers/mod.rs +++ b/rust/src/providers/mod.rs @@ -313,6 +313,44 @@ pub(crate) fn resolve_api_key( pub(crate) fn validated_https_url( raw: &str, label: &str, +) -> Result { + validated_endpoint_url(raw, label, false) +} + +/// Like [`validated_https_url`], but plain HTTP is also accepted for loopback, +/// private-network (RFC1918, unique-local), link-local and `.local` hosts +/// (upstream `https-or-private-network-http` endpoint policy). +pub(crate) fn validated_https_or_private_http_url( + raw: &str, + label: &str, +) -> Result { + validated_endpoint_url(raw, label, true) +} + +fn is_private_network_host(host: &str) -> bool { + let normalized = host.trim_end_matches('.').to_ascii_lowercase(); + if normalized == "localhost" || normalized.ends_with(".local") { + return true; + } + let ip_candidate = normalized + .strip_prefix('[') + .and_then(|value| value.strip_suffix(']')) + .unwrap_or(&normalized); + match ip_candidate.parse::() { + Ok(std::net::IpAddr::V4(ip)) => ip.is_loopback() || ip.is_private() || ip.is_link_local(), + Ok(std::net::IpAddr::V6(ip)) => { + ip.is_loopback() + || (ip.segments()[0] & 0xfe00) == 0xfc00 + || (ip.segments()[0] & 0xffc0) == 0xfe80 + } + Err(_) => false, + } +} + +fn validated_endpoint_url( + raw: &str, + label: &str, + allow_private_http: bool, ) -> Result { let trimmed = raw.trim(); if trimmed.is_empty() { @@ -339,14 +377,21 @@ pub(crate) fn validated_https_url( let host = url.host_str().ok_or_else(|| { crate::core::ProviderError::Other(format!("{label} URL must include a host")) })?; - if url.scheme() != "https" + let scheme_ok = url.scheme() == "https" + || (allow_private_http && url.scheme() == "http" && is_private_network_host(host)); + if !scheme_ok || !url.username().is_empty() || url.password().is_some() || host.contains('%') || host.chars().any(|c| c.is_control() || c.is_whitespace()) { + let scheme_rule = if allow_private_http { + "HTTPS (or plain HTTP for loopback, private-network and .local hosts)" + } else { + "HTTPS" + }; return Err(crate::core::ProviderError::Other(format!( - "{label} URL must use HTTPS without user info or encoded host tricks" + "{label} URL must use {scheme_rule} without user info or encoded host tricks" ))); } Ok(url) diff --git a/rust/src/settings/api_keys.rs b/rust/src/settings/api_keys.rs index c2adf3565c..4c49c695fb 100644 --- a/rust/src/settings/api_keys.rs +++ b/rust/src/settings/api_keys.rs @@ -529,7 +529,9 @@ pub fn get_api_key_providers() -> Vec { name: "LLM Proxy", requires_api_key: true, api_key_env_var: Some("LLM_PROXY_API_KEY + LLM_PROXY_BASE_URL"), - api_key_help: Some("Set an LLM Proxy API key and base URL for quota-stats."), + api_key_help: Some( + "Set an LLM Proxy API key and base URL (Settings or LLM_PROXY_BASE_URL) for quota-stats.", + ), config_file_path: None, dashboard_url: None, }, diff --git a/rust/src/settings/provider_workspace.rs b/rust/src/settings/provider_workspace.rs index 338747c8ef..1fbcbbf3b0 100644 --- a/rust/src/settings/provider_workspace.rs +++ b/rust/src/settings/provider_workspace.rs @@ -57,6 +57,7 @@ pub fn validate_provider_workspace_value( } ProviderId::LiteLLM => validate_token_endpoint(trimmed, "LiteLLM base URL", |_| true), ProviderId::Sub2Api => validate_sub2api_base_url(trimmed), + ProviderId::LLMProxy => validate_llmproxy_base_url(trimmed), _ => Ok(trimmed.to_string()), } } @@ -68,6 +69,13 @@ fn validate_sub2api_base_url(raw: &str) -> Result { } } +fn validate_llmproxy_base_url(raw: &str) -> Result { + match crate::providers::validated_https_or_private_http_url(raw, "LLM Proxy") { + Ok(url) => Ok(url.to_string()), + Err(err) => Err(err.to_string()), + } +} + fn validate_id( value: &str, label: &str, @@ -219,6 +227,33 @@ mod tests { } } + #[test] + fn validates_llmproxy_base_url() { + assert_eq!( + validate_provider_workspace_value( + ProviderId::LLMProxy, + " https://proxy.example.com/v1?team=a " + ) + .unwrap(), + "https://proxy.example.com/v1?team=a" + ); + assert!( + validate_provider_workspace_value(ProviderId::LLMProxy, "http://192.168.1.10:8000") + .is_ok() + ); + assert!( + validate_provider_workspace_value(ProviderId::LLMProxy, "http://proxy.example.com") + .is_err() + ); + assert!( + validate_provider_workspace_value( + ProviderId::LLMProxy, + "https://user:pass@proxy.example.com" + ) + .is_err() + ); + } + #[test] fn validates_sub2api_base_url() { assert_eq!( From c49c894f6383b7f831b944710109e1f39454b083 Mon Sep 17 00:00:00 2001 From: RCD <90105158+Finesssee@users.noreply.github.com> Date: Thu, 1 Oct 2026 19:24:57 +0700 Subject: [PATCH 2/3] Port upstream 0.64.0: LLM Proxy informational rate windows and cost resets --- rust/src/providers/llmproxy/mod.rs | 267 +++++++++++++++++++++++++---- rust/src/providers/mod.rs | 15 +- 2 files changed, 249 insertions(+), 33 deletions(-) diff --git a/rust/src/providers/llmproxy/mod.rs b/rust/src/providers/llmproxy/mod.rs index 5aaaca339c..f428ff6d19 100644 --- a/rust/src/providers/llmproxy/mod.rs +++ b/rust/src/providers/llmproxy/mod.rs @@ -153,8 +153,8 @@ impl LLMProxyProvider { })?; let summary = parse_summary(&body)?; let mut result = ProviderFetchResult::new(snapshot_from_summary(&summary), "api"); - if let Some(cost) = summary.approximate_cost_usd { - result = result.with_cost(CostSnapshot::new(cost, "USD", "Approx. spend")); + if let Some(cost) = cost_from_summary(&summary) { + result = result.with_cost(cost); } Ok(result) } @@ -361,27 +361,27 @@ fn parse_summary(data: &[u8]) -> Result { } fn snapshot_from_summary(summary: &LLMProxySummary) -> UsageSnapshot { - let used_percent = summary + // Upstream leaves the primary lane out when no quota group reports a + // remaining percentage. A Windows snapshot always has a primary lane, so + // it becomes informational instead of a made-up 0%. + let primary = summary .minimum_remaining_percent - .map(|remaining| (100.0 - remaining).clamp(0.0, 100.0)) - .unwrap_or(0.0); - let mut primary = RateWindow::with_details(used_percent, None, summary.next_reset_at, None); - primary.reset_description = summary - .minimum_remaining_percent - .map(|remaining| format!("{remaining:.1}% minimum remaining")); - - let secondary = RateWindow::with_details( - 0.0, - None, - None, - Some(format!("{} requests", format_count(summary.total_requests))), - ); - let tertiary = RateWindow::with_details( - 0.0, - None, - None, - Some(format!("{} tokens", format_count(summary.total_tokens))), - ); + .map(|remaining| { + RateWindow::with_details( + (100.0 - remaining).clamp(0.0, 100.0), + None, + summary.next_reset_at, + None, + ) + }) + .unwrap_or_else(|| RateWindow::informational("No quota reported")); + + // Totals and provider rows are counts, not quotas. Informational rows keep + // them from rendering as empty bars with a "Resets" prefix. + let secondary = + RateWindow::informational(format!("{} requests", format_count(summary.total_requests))); + let tertiary = + RateWindow::informational(format!("{} tokens", format_count(summary.total_tokens))); let mut snapshot = UsageSnapshot::new(primary) .with_secondary(secondary) @@ -393,24 +393,44 @@ fn snapshot_from_summary(summary: &LLMProxySummary) -> UsageSnapshot { .with_organization(format!("{} providers", summary.provider_count)); for provider in summary.top_providers.iter().take(3) { - let mut detail = format!( - "{} req / {} tok", - format_count(provider.requests), - format_count(provider.tokens) - ); + let mut parts = vec![ + format!("{} req", format_count(provider.requests)), + format!("{} tok", format_count(provider.tokens)), + ]; if let Some(cost) = provider.approximate_cost_usd { - detail.push_str(&format!(" / ${cost:.2}")); + parts.push(format_usd(cost)); } snapshot = snapshot.with_extra_rate_window( provider.name.clone(), provider.name.clone(), - RateWindow::with_details(0.0, None, None, Some(detail)), + RateWindow::informational(parts.join(" · ")), ); } snapshot } +/// Approximate spend, kept even at $0. Upstream gives it the soonest quota +/// reset as its reset time. +fn cost_from_summary(summary: &LLMProxySummary) -> Option { + let cost = CostSnapshot::new(summary.approximate_cost_usd?, "USD", "Approx. spend"); + Some(match summary.next_reset_at { + Some(resets_at) => cost.with_resets_at(resets_at), + None => cost, + }) +} + +/// US-dollar amount with thousands separators and cents, like `$1,234.50`. +fn format_usd(value: f64) -> String { + #[allow( + clippy::cast_possible_truncation, + reason = "f64-to-u64 casts saturate in Rust; display amounts never approach u64::MAX" + )] + let cents = (value.abs() * 100.0).round() as u64; + let sign = if value < 0.0 && cents > 0 { "-" } else { "" }; + format!("{sign}${}.{:02}", format_count(cents / 100), cents % 100) +} + fn token_total(tokens: Option<&TokenStats>) -> u64 { tokens .map(|tokens| { @@ -524,6 +544,183 @@ mod tests { assert_eq!(snapshot.extra_rate_windows.len(), 2); } + fn utc(raw: &str) -> DateTime { + DateTime::parse_from_rfc3339(raw) + .unwrap() + .with_timezone(&Utc) + } + + /// Upstream `LLMProxyUsageFetcherTests` "parses quota stats summary", with + /// the reset moved into the future because the port reads the real clock. + #[test] + fn snapshot_matches_upstream_quota_stats_summary() { + let summary = parse_summary( + br#"{ + "providers": { + "openai": { + "credential_count": 3, "active_count": 2, "exhausted_count": 1, + "total_requests": 120, + "tokens": {"input_cached": 1000, "input_uncached": 2000, "output": 3000}, + "approx_cost": 12.5, + "quota_groups": {"default": {"remaining_percent": 42, "reset_time": "2099-05-18T12:00:00Z"}} + }, + "anthropic": { + "credential_count": 1, "active_count": 1, "exhausted_count": 0, + "total_requests": 40, + "tokens": {"input_cached": 0, "input_uncached": 500, "output": 500}, + "approx_cost": 3.0, + "quota_groups": [{"remaining_percent": 80}] + } + }, + "summary": {"total_requests": 160, "total_tokens": 7000, "approx_cost": 15.5} + }"#, + ) + .unwrap(); + let snapshot = snapshot_from_summary(&summary); + + assert!(!snapshot.primary.is_informational); + assert_eq!(snapshot.primary.used_percent, 58.0); + assert_eq!( + snapshot.primary.resets_at, + Some(utc("2099-05-18T12:00:00Z")) + ); + assert_eq!(snapshot.primary.reset_description, None); + + let secondary = snapshot.secondary.as_ref().unwrap(); + assert!(secondary.is_informational); + assert_eq!(secondary.reset_description.as_deref(), Some("160 requests")); + let tertiary = snapshot.tertiary.as_ref().unwrap(); + assert!(tertiary.is_informational); + assert_eq!(tertiary.reset_description.as_deref(), Some("7,000 tokens")); + + let first = &snapshot.extra_rate_windows[0]; + assert_eq!(first.id, "openai"); + assert!(first.window.is_informational); + assert_eq!( + first.window.reset_description.as_deref(), + Some("120 req · 6,000 tok · $12.50") + ); + assert_eq!( + snapshot.extra_rate_windows[1] + .window + .reset_description + .as_deref(), + Some("40 req · 1,000 tok · $3.00") + ); + + let cost = cost_from_summary(&summary).unwrap(); + assert_eq!(cost.used, 15.5); + assert_eq!(cost.currency_code, "USD"); + assert_eq!(cost.period, "Approx. spend"); + assert_eq!(cost.resets_at, Some(utc("2099-05-18T12:00:00Z"))); + } + + /// Upstream "zero summary spend is retained and empty providers stay + /// displayable": no quota groups means no quota lane (here: informational). + #[test] + fn empty_providers_keep_zero_spend_without_a_quota_lane() { + let summary = parse_summary(br#"{"providers":{},"summary":{"approx_cost":0}}"#).unwrap(); + let snapshot = snapshot_from_summary(&summary); + + assert!(snapshot.primary.is_informational); + assert_eq!( + snapshot.primary.reset_description.as_deref(), + Some("No quota reported") + ); + assert_eq!( + snapshot + .secondary + .as_ref() + .unwrap() + .reset_description + .as_deref(), + Some("0 requests") + ); + assert!(snapshot.extra_rate_windows.is_empty()); + let cost = cost_from_summary(&summary).unwrap(); + assert_eq!(cost.used, 0.0); + assert_eq!(cost.resets_at, None); + } + + /// Upstream "sums missing summary ignores elapsed resets and limits sorted + /// provider rows", with the future reset moved past the real clock. + #[test] + fn sums_missing_summary_ignores_elapsed_resets_and_keeps_top_three() { + let summary = parse_summary( + br#"{"providers":{ + "delta":{"total_requests":1,"tokens":{"output":2},"approx_cost":0}, + "charlie":{"total_requests":3,"approx_cost":2,"quota_groups":"invalid"}, + "bravo":{"total_requests":3,"quota_groups":[{"remaining_percent":-10,"reset_time":"1970-01-01T00:00:00Z"}]}, + "alpha":{"total_requests":4,"approx_cost":3,"quota_groups":{"a":{"reset_time":"2099-05-01T00:00:00Z"}}} + }}"#, + ) + .unwrap(); + let snapshot = snapshot_from_summary(&summary); + + assert_eq!(snapshot.primary.used_percent, 100.0); + assert_eq!( + snapshot.primary.resets_at, + Some(utc("2099-05-01T00:00:00Z")) + ); + assert_eq!( + snapshot + .secondary + .as_ref() + .unwrap() + .reset_description + .as_deref(), + Some("11 requests") + ); + assert_eq!( + snapshot + .tertiary + .as_ref() + .unwrap() + .reset_description + .as_deref(), + Some("2 tokens") + ); + assert_eq!(cost_from_summary(&summary).unwrap().used, 5.0); + let ids: Vec<_> = snapshot + .extra_rate_windows + .iter() + .map(|row| row.id.as_str()) + .collect(); + assert_eq!(ids, ["alpha", "bravo", "charlie"]); + assert_eq!( + snapshot.extra_rate_windows[1] + .window + .reset_description + .as_deref(), + Some("3 req · 0 tok") + ); + } + + #[test] + fn rejects_malformed_payloads() { + let bodies: [&[u8]; 3] = [ + b"not json", + b"{}", + br#"{"providers":{"a":{"total_requests":"3"}}}"#, + ]; + for body in bodies { + assert!( + matches!(parse_summary(body), Err(ProviderError::Parse(_))), + "parsed {}", + String::from_utf8_lossy(body) + ); + } + } + + #[test] + fn formats_usd_with_thousands_separators() { + assert_eq!(format_usd(12.5), "$12.50"); + assert_eq!(format_usd(1234.5), "$1,234.50"); + assert_eq!(format_usd(0.0), "$0.00"); + assert_eq!(format_usd(0.004), "$0.00"); + assert_eq!(format_usd(-3.0), "-$3.00"); + } + fn stats_url(raw: &str) -> String { let base = crate::providers::validated_https_or_private_http_url(raw, "LLM Proxy").unwrap(); quota_stats_url(base).to_string() @@ -586,7 +783,9 @@ mod tests { "http://192.168.1.10:8000", "http://169.254.1.1", "http://proxy.local", + "http://printer.local.:8000", "http://[fd00::1]", + "http://[fe80::1]", ] { assert!( crate::providers::validated_https_or_private_http_url(ok, "LLM Proxy").is_ok(), @@ -600,6 +799,16 @@ mod tests { "ftp://proxy.local", "https://user:pass@proxy.example.com", "http://user@192.168.1.10", + // `.local` needs a label in front of it. + "http://.local", + "http://.local.", + "http://app.localhost:8000", + // Upstream compares `localhost` before dropping a trailing dot. + "http://localhost.:8000", + // IPv4-mapped and global IPv6 addresses are not private literals. + "http://[::ffff:10.0.0.1]", + "http://[2001:db8::1]", + "http://proxy.local%2f.evil.test", "", ] { assert!( diff --git a/rust/src/providers/mod.rs b/rust/src/providers/mod.rs index 2b48e87f4b..ceb40fc9dd 100755 --- a/rust/src/providers/mod.rs +++ b/rust/src/providers/mod.rs @@ -328,14 +328,21 @@ pub(crate) fn validated_https_or_private_http_url( } fn is_private_network_host(host: &str) -> bool { - let normalized = host.trim_end_matches('.').to_ascii_lowercase(); - if normalized == "localhost" || normalized.ends_with(".local") { + let host = host.to_ascii_lowercase(); + // Upstream `isPrivateNetworkHost`: `localhost` exactly, or a non-empty + // label before `.local` once one trailing dot is dropped. + let hostname = host.strip_suffix('.').unwrap_or(&host); + if host == "localhost" + || hostname + .strip_suffix(".local") + .is_some_and(|label| !label.is_empty()) + { return true; } - let ip_candidate = normalized + let ip_candidate = host .strip_prefix('[') .and_then(|value| value.strip_suffix(']')) - .unwrap_or(&normalized); + .unwrap_or(&host); match ip_candidate.parse::() { Ok(std::net::IpAddr::V4(ip)) => ip.is_loopback() || ip.is_private() || ip.is_link_local(), Ok(std::net::IpAddr::V6(ip)) => { From 90e07b4e8d19aab8ee462533417ed246a453ad22 Mon Sep 17 00:00:00 2001 From: RCD <90105158+Finesssee@users.noreply.github.com> Date: Thu, 1 Oct 2026 19:25:11 +0700 Subject: [PATCH 3/3] chore: silence clippy 1.96 nonminimal-bool and manual-range-contains lints --- rust/src/providers/alibabatokenplan/cli.rs | 2 +- rust/src/providers/kiro/usage_limits.rs | 4 ++-- rust/src/providers/openai/subscription.rs | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/rust/src/providers/alibabatokenplan/cli.rs b/rust/src/providers/alibabatokenplan/cli.rs index 09f76c1ea9..7bac1ee840 100644 --- a/rust/src/providers/alibabatokenplan/cli.rs +++ b/rust/src/providers/alibabatokenplan/cli.rs @@ -160,7 +160,7 @@ fn reset_date(value: Option<&Value>) -> Option> { return None; } let rounded = milliseconds.round(); - if rounded < 1.0 || rounded >= 9_223_372_036_854_775_808.0 { + if !(1.0..9_223_372_036_854_775_808.0).contains(&rounded) { return None; } let millis = format!("{rounded:.0}").parse::().ok()?; diff --git a/rust/src/providers/kiro/usage_limits.rs b/rust/src/providers/kiro/usage_limits.rs index 9e12206427..a90a4081ed 100644 --- a/rust/src/providers/kiro/usage_limits.rs +++ b/rust/src/providers/kiro/usage_limits.rs @@ -312,9 +312,9 @@ fn endpoint_for_profile_arn(profile_arn: &str) -> Option<&'static str> { || fields[0] != "arn" || fields[1] != "aws" || fields[2] != "codewhisperer" - || !fields[5] + || fields[5] .strip_prefix("profile/") - .is_some_and(|name| !name.is_empty()) + .is_none_or(|name| name.is_empty()) { return None; } diff --git a/rust/src/providers/openai/subscription.rs b/rust/src/providers/openai/subscription.rs index 1b9be905a2..a9621768d1 100644 --- a/rust/src/providers/openai/subscription.rs +++ b/rust/src/providers/openai/subscription.rs @@ -203,7 +203,9 @@ pub fn parse_subscription_value(value: &Value) -> OpenAISubscriptionFetchResult ))) } (None, Some(false)) => OpenAISubscriptionFetchResult::Success( - (!starts_at.is_none()).then(|| SubscriptionMetadata::new(starts_at, None, None)), + starts_at + .is_some() + .then(|| SubscriptionMetadata::new(starts_at, None, None)), ), // A renewal without an explicit active-until date is not safe to // represent as a date, even if a plan is known.