diff --git a/apps/desktop-tauri/src/components/MenuCardDetails.tsx b/apps/desktop-tauri/src/components/MenuCardDetails.tsx index 7cd5ca78f0..5051fb56c4 100644 --- a/apps/desktop-tauri/src/components/MenuCardDetails.tsx +++ b/apps/desktop-tauri/src/components/MenuCardDetails.tsx @@ -4,6 +4,7 @@ import type { DailyCostPoint, ProviderDisplayDetail, PaceSnapshot, + ProviderInventoryItem, ProviderChartData, ProviderLocalUsageSummary, ProviderUsageSnapshot, @@ -595,6 +596,14 @@ export default function MenuCardDetails({ )} + {!provider.error && hasDisplayDetails && ( +
+ {provider.displayDetails?.map((detail, index) => ( + + ))} +
+ )} + {wayfinderUsage && !compactOverview && } {!compactOverview && hasMetrics && hasCost &&
} diff --git a/rust/src/cli/diagnose.rs b/rust/src/cli/diagnose.rs index 10e5ca2c00..2583604bb3 100644 --- a/rust/src/cli/diagnose.rs +++ b/rust/src/cli/diagnose.rs @@ -83,6 +83,10 @@ struct ProviderDiagnosticFetchAttempt { kind: String, was_available: bool, error_category: Option, + #[serde(skip_serializing_if = "Option::is_none")] + strategy_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + strategy_outcome: Option, } #[derive(Debug, Serialize)] @@ -199,6 +203,8 @@ async fn collect_provider_diagnostic( kind: source_mode_name(source_mode).to_string(), was_available: true, error_category: None, + strategy_id: final_strategy_id(provider_id, Some(&result.source_label)), + strategy_outcome: final_strategy_outcome(provider_id, true), }], ) } @@ -215,6 +221,8 @@ async fn collect_provider_diagnostic( kind: source_mode_name(source_mode).to_string(), was_available: false, error_category: Some(category.to_string()), + strategy_id: None, + strategy_outcome: final_strategy_outcome(provider_id, false), }], ) } @@ -360,6 +368,19 @@ fn cost_present(cost: Option<&CostSnapshot>) -> bool { cost.is_some() } +fn final_strategy_id(provider_id: ProviderId, source_label: Option<&str>) -> Option { + (provider_id == ProviderId::Antigravity) + .then_some(source_label) + .flatten() + .and_then(crate::providers::antigravity::strategy_from_source_label) + .map(|strategy| strategy.as_str().to_owned()) +} + +fn final_strategy_outcome(provider_id: ProviderId, succeeded: bool) -> Option { + (provider_id == ProviderId::Antigravity) + .then(|| if succeeded { "success" } else { "error" }.to_string()) +} + fn source_mode_name(mode: SourceMode) -> &'static str { match mode { SourceMode::Auto => "auto", @@ -413,6 +434,28 @@ mod tests { assert_eq!(source_mode_name(SourceMode::Cli), "cli"); } + #[test] + fn antigravity_diagnostics_report_only_the_final_strategy() { + assert_eq!( + final_strategy_id(ProviderId::Antigravity, Some("cli")), + Some("cli".to_string()) + ); + assert_eq!( + final_strategy_outcome(ProviderId::Antigravity, true), + Some("success".to_string()) + ); + assert_eq!( + final_strategy_outcome(ProviderId::Antigravity, false), + Some("error".to_string()) + ); + assert_eq!( + final_strategy_id(ProviderId::Antigravity, Some("unknown")), + None + ); + assert_eq!(final_strategy_id(ProviderId::Grok, Some("cli")), None); + assert_eq!(final_strategy_outcome(ProviderId::Grok, true), None); + } + #[test] fn diagnostic_usage_summary_does_not_export_identity_values() { let usage = UsageSnapshot::new(RateWindow::new(42.0)) diff --git a/rust/src/providers/antigravity/cli_fallback.rs b/rust/src/providers/antigravity/cli_fallback.rs index feef3606eb..ed3572358d 100644 --- a/rust/src/providers/antigravity/cli_fallback.rs +++ b/rust/src/providers/antigravity/cli_fallback.rs @@ -169,7 +169,10 @@ async fn fetch_print_usage(binary: &Path) -> Result AsyncCommand { diff --git a/rust/src/providers/antigravity/mod.rs b/rust/src/providers/antigravity/mod.rs index 9496275814..5f35764663 100755 --- a/rust/src/providers/antigravity/mod.rs +++ b/rust/src/providers/antigravity/mod.rs @@ -69,6 +69,32 @@ pub struct AntigravityProvider { metadata: ProviderMetadata, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum AntigravityStrategyId { + Local, + Cli, + Offline, +} + +impl AntigravityStrategyId { + pub(crate) const fn as_str(self) -> &'static str { + match self { + Self::Local => "local", + Self::Cli => "cli", + Self::Offline => "offline", + } + } +} + +pub(crate) fn strategy_from_source_label(source_label: &str) -> Option { + match source_label { + "local" => Some(AntigravityStrategyId::Local), + "cli" => Some(AntigravityStrategyId::Cli), + "offline" => Some(AntigravityStrategyId::Offline), + _ => None, + } +} + /// Return a regex that matches `-- ` or `--=`. fn flag_re(flag: &str) -> Regex { Regex::new(&format!("--{f}(?:\\s+|\\s*=\\s*)(\\S+)", f = flag)).expect("valid flag pattern") @@ -411,7 +437,7 @@ impl AntigravityProvider { { legacy_status::apply_user_identity(&mut snapshot, &identity); } - return Ok(Self::fetch_result(snapshot, "local")); + return Ok(Self::fetch_result(snapshot, AntigravityStrategyId::Local)); } Err(error) => tracing::debug!( %error, @@ -444,11 +470,14 @@ impl AntigravityProvider { let response: UserStatusResponse = serde_json::from_slice(&bytes) .map_err(|e| ProviderError::Parse(format!("Failed to parse response: {e}")))?; self.parse_user_status(response) - .map(|usage| Self::fetch_result(usage, "local")) + .map(|usage| Self::fetch_result(usage, AntigravityStrategyId::Local)) } - pub(super) fn fetch_result(usage: UsageSnapshot, source_label: &str) -> ProviderFetchResult { - ProviderFetchResult::new(Self::with_cadence_labels(usage), source_label) + pub(super) fn fetch_result( + usage: UsageSnapshot, + strategy: AntigravityStrategyId, + ) -> ProviderFetchResult { + ProviderFetchResult::new(Self::with_cadence_labels(usage), strategy.as_str()) } async fn try_print_usage_fallback(&self) -> Result, ProviderError> { @@ -607,7 +636,10 @@ impl AntigravityProvider { "Offline ยท {count} {noun}" ))) .with_login_method("offline"); - Some(ProviderFetchResult::new(usage, "offline")) + Some(ProviderFetchResult::new( + usage, + AntigravityStrategyId::Offline.as_str(), + )) } /// Resolve a failure to obtain live usage. @@ -641,7 +673,7 @@ impl AntigravityProvider { match outcome { Ok(ManagedAgyOutcome::Reused(result)) => Ok(Some(result)), Ok(ManagedAgyOutcome::Fetched(mut result)) => { - result.source_label = "cli".to_string(); + result.source_label = AntigravityStrategyId::Cli.as_str().to_string(); Ok(Some(result)) } Ok(ManagedAgyOutcome::Missing) => Ok(None), @@ -710,7 +742,7 @@ impl AntigravityProvider { match self.fetch_with_managed_agy().await { Ok(ManagedAgyOutcome::Reused(result)) => return Ok(result), Ok(ManagedAgyOutcome::Fetched(mut result)) => { - result.source_label = "cli".to_string(); + result.source_label = AntigravityStrategyId::Cli.as_str().to_string(); return Ok(result); } Ok(ManagedAgyOutcome::Missing) => {} diff --git a/rust/src/providers/antigravity/tests.rs b/rust/src/providers/antigravity/tests.rs index 5c15855bfe..f18a9059c6 100644 --- a/rust/src/providers/antigravity/tests.rs +++ b/rust/src/providers/antigravity/tests.rs @@ -555,7 +555,25 @@ const STRUCTURED_CLI_USAGE_REPORT: &[u8] = br#"{ fn structured_cli_result() -> ProviderFetchResult { let usage = quota_summary::parse_cli_usage_report(STRUCTURED_CLI_USAGE_REPORT) .expect("structured CLI fixture should parse"); - AntigravityProvider::fetch_result(usage, "cli") + AntigravityProvider::fetch_result(usage, AntigravityStrategyId::Cli) +} + +#[test] +fn strategy_ids_are_stable_and_reject_unknown_sources() { + assert_eq!( + strategy_from_source_label("local"), + Some(AntigravityStrategyId::Local) + ); + assert_eq!( + strategy_from_source_label("cli"), + Some(AntigravityStrategyId::Cli) + ); + assert_eq!( + strategy_from_source_label("offline"), + Some(AntigravityStrategyId::Offline) + ); + assert_eq!(strategy_from_source_label("managed"), None); + assert_eq!(AntigravityStrategyId::Cli.as_str(), "cli"); } fn offline_result() -> ProviderFetchResult {