diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index d55efe6ac0..1a18e4fffe 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -932,6 +932,7 @@ The Antigravity quota summary picks one Gemini row for the primary and secondary - The ordinary Gemini buckets win: `gemini-5h` (or a session alias such as `gemini-session`) and `gemini-weekly`. Their IDs are matched after lowercasing, with `_` read as `-` and a trailing ` limit` ignored. Display labels never decide this. - A model-specific Gemini allowance, such as `gemini-2.5-pro-5h`, represents Gemini only when the ordinary bucket of that cadence is absent, unknown or disabled. - Every bucket stays visible as an extra window with its own usage and reset. +- A group named like "Gemini" with no digit is shown as **Gemini** (for example **Gemini weekly**). A versioned Gemini group keeps its own name from the usage response, so a separate Gemini 4 Argon group is shown as **Gemini 4 Argon weekly**, alongside the ordinary **Gemini weekly** quota (upstream #4585). ## Window lengths that match the Mac diff --git a/rust/src/providers/antigravity/quota_summary.rs b/rust/src/providers/antigravity/quota_summary.rs index 7337a554c0..30e91f6671 100644 --- a/rust/src/providers/antigravity/quota_summary.rs +++ b/rust/src/providers/antigravity/quota_summary.rs @@ -361,11 +361,28 @@ fn group_rank(group: &QuotaSummaryGroup) -> u8 { group_family(group).1 } +/// Upstream `displayTitle(forQuotaGroup:)`. A Gemini group reads "Gemini" only +/// when its title has no digit, so a versioned group such as "Gemini 4 Argon" +/// keeps its own name. Claude and GPT groups read "Claude/GPT"; any other group +/// keeps its trimmed name, or "Quota" when the name is blank. +/// +/// Unlike upstream, every Gemini-ranked group is titled in the Gemini branch, +/// so a versioned "Gemini 4 + GPT" keeps its name instead of "Claude/GPT". fn group_title(group: &QuotaSummaryGroup) -> String { - match group_family(group) { - (_, 0) => "Gemini".into(), - (_, 1) => "Claude/GPT".into(), - (title, _) => title.unwrap_or("Quota").to_string(), + let (title, rank) = group_family(group); + let title = title.unwrap_or_default(); + if rank == 0 { + if title.chars().any(char::is_numeric) { + title.into() + } else { + "Gemini".into() + } + } else if rank == 1 { + "Claude/GPT".into() + } else if title.is_empty() { + "Quota".into() + } else { + title.into() } } @@ -702,11 +719,51 @@ mod tests { ); } + #[test] + fn cli_report_keeps_a_versioned_gemini_group_name_distinct() { + // Upstream #4585: "Gemini 4 Argon" is its own quota, not a second "Gemini". + let data = br#"{ + "status": "SUCCESS", + "command": {"name": "usage", "data": {"groups": [ + {"name": "Gemini Models", "buckets": [ + {"id": "gemini-weekly", "name": "Weekly Limit Remaining", "window": "weekly", "remaining_fraction": 0.99} + ]}, + {"name": "Gemini 4 Argon", "description": "For paid Pro plans, Argon quota is separate.", "buckets": [ + {"id": "gemini-argon-wtu-weekly", "name": "Gemini 4 Argon Weekly Limit", "window": "weekly", "remaining_fraction": 0.53, "reset_time": "2026-10-23T10:24:14Z"} + ]} + ]}} + }"#; + + let snapshot = parse_cli_usage_report(data).expect("CLI report"); + + assert_eq!(snapshot.secondary_label.as_deref(), Some("Gemini Weekly")); + let extra_titles: Vec<&str> = snapshot + .extra_rate_windows + .iter() + .map(|window| window.title.as_str()) + .collect(); + assert_eq!(extra_titles, ["Gemini 4 Argon weekly"]); + } + #[test] fn group_rank_title_and_scope_follow_the_group_family() { // (displayName, name, rank, title, scope) let cases = [ (Some("Gemini Pro"), None, 0, "Gemini", "gemini"), + ( + Some("Gemini 4 Argon"), + None, + 0, + "Gemini 4 Argon", + "gemini-4-argon", + ), + ( + Some("Gemini 4 + GPT"), + None, + 0, + "Gemini 4 + GPT", + "gemini-4---gpt", + ), (None, Some("claude & GPT"), 1, "Claude/GPT", "claude-gpt"), ( Some("OpenAI gpt-oss"),