Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ pub struct ProviderUsageSnapshot {
pub account_organization: Option<String>,
pub tray_status_label: Option<String>,
pub fetch_duration_ms: Option<u128>,
pub wayfinder_usage: Option<codexbar::core::WayfinderUsageSnapshot>,
}

pub(crate) fn filter_hidden_codex_spark_rows(
Expand Down Expand Up @@ -211,6 +212,7 @@ impl ProviderUsageSnapshot {
account_organization: usage.account_organization.clone(),
tray_status_label: None,
fetch_duration_ms: None,
wayfinder_usage: result.wayfinder_usage.clone(),
}
}

Expand Down Expand Up @@ -247,6 +249,7 @@ impl ProviderUsageSnapshot {
account_organization: None,
tray_status_label: None,
fetch_duration_ms: None,
wayfinder_usage: None,
}
}
}
Expand Down Expand Up @@ -433,6 +436,7 @@ pub struct SettingsSnapshot {
claude_avoid_keychain_prompts: bool,
codex_spark_usage_visible: bool,
disable_keychain_access: bool,
wayfinder_gateway_url: String,
provider_metrics: std::collections::HashMap<String, &'static str>,
float_bar_enabled: bool,
float_bar_opacity: u8,
Expand Down Expand Up @@ -469,6 +473,7 @@ impl From<Settings> for SettingsSnapshot {
fn from(settings: Settings) -> Self {
let avoid_keychain_prompts = settings.claude_avoid_keychain_prompts();
let codex_spark_usage_visible = settings.codex_spark_usage_visible();
let wayfinder_gateway_url = settings.gateway_url(ProviderId::Wayfinder).to_string();

let provider_order = settings.provider_display_order_names();
let enabled_providers = provider_order
Expand Down Expand Up @@ -518,6 +523,7 @@ impl From<Settings> for SettingsSnapshot {
claude_avoid_keychain_prompts: avoid_keychain_prompts,
codex_spark_usage_visible,
disable_keychain_access: settings.disable_keychain_access,
wayfinder_gateway_url,
provider_metrics,
float_bar_enabled: settings.float_bar_enabled,
float_bar_opacity: settings.float_bar_opacity,
Expand Down
25 changes: 25 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/provider_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,31 @@ pub fn get_provider_workspace_id(provider_id: String) -> Result<Option<String>,
Ok((!value.is_empty()).then_some(value))
}

fn gateway_provider(provider_id: &str) -> Option<codexbar::core::ProviderId> {
(provider_id == "wayfinder").then_some(codexbar::core::ProviderId::Wayfinder)
}

#[tauri::command]
pub fn get_provider_gateway_url(provider_id: String) -> Result<Option<String>, String> {
let Some(id) = gateway_provider(&provider_id) else {
return Ok(None);
};
Ok(Some(Settings::load().gateway_url(id).to_string()))
}

#[tauri::command]
pub fn set_provider_gateway_url(provider_id: String, gateway_url: String) -> Result<(), String> {
let id = gateway_provider(&provider_id)
.ok_or_else(|| format!("Provider '{provider_id}' does not expose a gateway URL"))?;
let gateway_url = gateway_url.trim();
codexbar::providers::wayfinder::parse_gateway_url(gateway_url)
.map_err(|error| error.to_string())?;

let mut settings = Settings::load();
settings.set_gateway_url(id, gateway_url.to_string());
settings.save().map_err(|error| error.to_string())
}

// ── Phase 6c — cookie source & region option catalogs ────────────────

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,16 @@ pub(crate) fn build_fetch_context(

let workspace_id = settings.workspace_id(id).trim().to_string();
let api_region = settings.api_region(id).trim().to_string();
let gateway_url = (id == ProviderId::Wayfinder && !settings.gateway_url(id).is_empty())
.then(|| settings.gateway_url(id).to_string());

FetchContext {
source_mode,
manual_cookie_header: cookie_header,
api_key,
workspace_id: (!workspace_id.is_empty()).then_some(workspace_id),
api_region: (!api_region.is_empty()).then_some(api_region),
gateway_url,
..FetchContext::default()
}
}
Expand Down
6 changes: 6 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ fn provider_cache_upsert_replaces_existing_provider() {
let result = ProviderFetchResult {
usage: codexbar::core::UsageSnapshot::new(codexbar::core::RateWindow::new(10.0)),
cost: None,
wayfinder_usage: None,
source_label: "CLI".to_string(),
};
let mut first = ProviderUsageSnapshot::from_fetch_result(ProviderId::Codex, &metadata, &result);
Expand All @@ -701,6 +702,7 @@ fn hiding_codex_spark_rows_preserves_other_extra_usage() {
let result = ProviderFetchResult {
usage: codexbar::core::UsageSnapshot::new(codexbar::core::RateWindow::new(10.0)),
cost: None,
wayfinder_usage: None,
source_label: "CLI".to_string(),
};
let mut snapshot =
Expand Down Expand Up @@ -730,6 +732,7 @@ fn claude_transient_auth_failure_preserves_first_last_good_snapshot() {
let result = ProviderFetchResult {
usage: codexbar::core::UsageSnapshot::new(codexbar::core::RateWindow::new(42.0)),
cost: None,
wayfinder_usage: None,
source_label: "OAuth".to_string(),
};
let good = ProviderUsageSnapshot::from_fetch_result(ProviderId::Claude, &metadata, &result);
Expand Down Expand Up @@ -757,6 +760,7 @@ fn claude_repeated_auth_failure_surfaces_error() {
let result = ProviderFetchResult {
usage: codexbar::core::UsageSnapshot::new(codexbar::core::RateWindow::new(42.0)),
cost: None,
wayfinder_usage: None,
source_label: "OAuth".to_string(),
};
let good = ProviderUsageSnapshot::from_fetch_result(ProviderId::Claude, &metadata, &result);
Expand Down Expand Up @@ -896,6 +900,7 @@ fn japanese_provider_snapshot_localizes_weekly_label() {
let result = ProviderFetchResult {
usage,
cost: None,
wayfinder_usage: None,
source_label: "OAuth".to_string(),
};

Expand Down Expand Up @@ -923,6 +928,7 @@ fn japanese_provider_snapshot_localizes_pace_reserve_description() {
let result = ProviderFetchResult {
usage,
cost: None,
wayfinder_usage: None,
source_label: "OAuth".to_string(),
};

Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ fn main() {
commands::get_provider_region,
commands::get_provider_region_options,
commands::set_provider_workspace_id,
commands::get_provider_gateway_url,
commands::set_provider_gateway_url,
commands::get_provider_workspace_id,
commands::get_gemini_cli_signed_in,
commands::get_vertexai_status,
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src-tauri/src/powertoys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ mod tests {
account_organization: Some("Example Org".to_string()),
tray_status_label: None,
fetch_duration_ms: None,
wayfinder_usage: None,
});
let value = serde_json::to_value(snapshot).unwrap();

Expand Down Expand Up @@ -244,6 +245,7 @@ mod tests {
account_organization: None,
tray_status_label: None,
fetch_duration_ms: None,
wayfinder_usage: None,
});
let value = serde_json::to_value(snapshot).unwrap();

Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src-tauri/src/tray_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,7 @@ mod tests {
account_organization: None,
tray_status_label: None,
fetch_duration_ms: None,
wayfinder_usage: None,
}
}

Expand Down
44 changes: 44 additions & 0 deletions apps/desktop-tauri/src/components/MenuCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ describe("MenuCard", () => {
PanelThirtyDayTokens: "30d tokens",
PanelTodayBudget: "today",
PanelUsedSuffix: "used",
WayfinderGatewayStatus: "Gateway",
WayfinderModels: "Models",
WayfinderRequests: "Requests",
WayfinderTokens: "Tokens",
WayfinderSaved: "Saved",
WayfinderOffline: "Gateway offline",
WayfinderDryRun: "Dry run",
WayfinderMissingKeys: "Missing keys",
}),
);
tauriMocks.getProviderChartData.mockResolvedValue({
Expand Down Expand Up @@ -187,6 +195,42 @@ describe("MenuCard", () => {
expect(screen.getByText("58% left")).toBeInTheDocument();
});

it("renders Wayfinder telemetry without quota or identity rows", async () => {
const snapshot = provider(null);
snapshot.providerId = "wayfinder";
snapshot.displayName = "Wayfinder";
snapshot.accountEmail = "should-not-render@example.test";
snapshot.planName = "should-not-render";
snapshot.wayfinderUsage = {
gatewayStatus: "ok",
offline: false,
dryRun: false,
missingKeys: [],
modelCount: 2,
models: ["model-a", "model-b"],
requests: 14,
estimatedRequests: 0,
tokens: 1028,
realized: 0.004,
baseline: 0.01,
saved: 0.006,
savedPercent: 60,
periodDays: 30,
unit: "usd",
priced: true,
routes: [],
};

renderCard(snapshot);

expect(await screen.findByText("ok")).toBeInTheDocument();
expect(screen.getByText("2")).toBeInTheDocument();
expect(screen.getByText("1K")).toBeInTheDocument();
expect(screen.queryByText("should-not-render@example.test")).not.toBeInTheDocument();
expect(screen.queryByText("should-not-render")).not.toBeInTheDocument();
expect(screen.queryByText("Session")).not.toBeInTheDocument();
});

it("notifies the tray panel after async local usage data loads", async () => {
const onLayoutChange = vi.fn();

Expand Down
70 changes: 62 additions & 8 deletions apps/desktop-tauri/src/components/MenuCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,51 @@ function LocalUsageBlock({
);
}

function WayfinderUsageBlock({
usage,
}: {
usage: NonNullable<ProviderUsageSnapshot["wayfinderUsage"]>;
}) {
const { t } = useLocale();
const formatAmount = (value: number) =>
usage.priced ? `${value.toFixed(4)} ${usage.unit.toUpperCase()}` : "—";

return (
<section className="menu-card__group">
<div className="menu-card__local-grid">
<div>
<span className="menu-card__local-label">{t("WayfinderGatewayStatus")}</span>
<strong>{usage.gatewayStatus}</strong>
</div>
<div>
<span className="menu-card__local-label">{t("WayfinderModels")}</span>
<strong>{usage.modelCount}</strong>
</div>
<div>
<span className="menu-card__local-label">{t("WayfinderRequests")}</span>
<strong>{formatCompactCount(usage.requests)}</strong>
</div>
<div>
<span className="menu-card__local-label">{t("WayfinderTokens")}</span>
<strong>{formatCompactCount(usage.tokens)}</strong>
</div>
</div>
<div className="menu-card__cost-line">
{t("WayfinderSaved")}: {formatAmount(usage.saved)} ({usage.savedPercent.toFixed(1)}%)
</div>
{(usage.offline || usage.dryRun || usage.missingKeys.length > 0) && (
<div className="menu-card__local-note">
{usage.offline && <span>{t("WayfinderOffline")}</span>}
{usage.dryRun && <span>{t("WayfinderDryRun")}</span>}
{usage.missingKeys.length > 0 && (
<span>{t("WayfinderMissingKeys")}: {usage.missingKeys.join(", ")}</span>
)}
</div>
)}
</section>
);
}

function displayPlanName(planName: string | null): string | null {
if (!planName) return null;
const normalized = planName.trim().toLowerCase();
Expand Down Expand Up @@ -397,19 +442,24 @@ export default function MenuCard({
};
}, [provider.providerId, provider.accountEmail, onLayoutChange]);

const email = provider.accountEmail
const isWayfinder = provider.providerId === "wayfinder";
const email = !isWayfinder && provider.accountEmail
? hideEmail
? maskEmail(provider.accountEmail)
: provider.accountEmail
: null;
const planName = displayPlanName(provider.planName);
const planName = !isWayfinder ? displayPlanName(provider.planName) : null;

const metrics: MetricEntry[] = [
{
id: "primary",
label: provider.primaryLabel ?? t("DetailWindowPrimary"),
snap: provider.primary,
},
...(isWayfinder
? []
: [
{
id: "primary",
label: provider.primaryLabel ?? t("DetailWindowPrimary"),
snap: provider.primary,
},
]),
];
if (provider.secondary)
metrics.push({
Expand Down Expand Up @@ -450,8 +500,10 @@ export default function MenuCard({
const hasMetrics = visibleMetrics.length > 0;
const hasCost = !!provider.cost;
const hasPace = !!provider.pace;
const wayfinderUsage = isWayfinder ? provider.wayfinderUsage : null;
const hasDetails =
!provider.error && (hasMetrics || hasCost || hasPace || hasCharts || !!localUsage);
!provider.error &&
(hasMetrics || hasCost || hasPace || hasCharts || !!localUsage || !!wayfinderUsage);
const cardClassName = [
"menu-card",
provider.error ? "menu-card--error" : null,
Expand Down Expand Up @@ -514,6 +566,8 @@ export default function MenuCard({
</section>
)}

{wayfinderUsage && <WayfinderUsageBlock usage={wayfinderUsage} />}

{localUsage && (
<LocalUsageBlock
providerId={provider.providerId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export const PROVIDER_ICON_REGISTRY: Record<string, ProviderIcon> = {
vertexai: { id: "vertexai", brandColor: "#4285f4", fallbackLetter: "△", svgPath: RAW.vertexai },
warp: { id: "warp", brandColor: "#6366f1", fallbackLetter: "W", svgPath: RAW.warp },
windsurf: { id: "windsurf", brandColor: "#22c55e", fallbackLetter: "W", svgPath: RAW.windsurf },
wayfinder: { id: "wayfinder", brandColor: "#14b8a6", fallbackLetter: "W" },
zai: { id: "zai", brandColor: "#e85a6a", fallbackLetter: "Z", svgPath: RAW.zai },
// Aliases / Rust-side normalizations without their own SVG.
nanogpt: { id: "nanogpt", brandColor: "#687fa1", fallbackLetter: "N" },
Expand Down
11 changes: 11 additions & 0 deletions apps/desktop-tauri/src/i18n/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ export const ALL_LOCALE_KEYS = [
"ProviderSourceGithubApiShort",
"ProviderSourceLocalShort",
"ProviderSourceKiroEnvShort",
"WayfinderGatewayTitle",
"WayfinderGatewayLabel",
"WayfinderGatewayHelp",
"WayfinderGatewayStatus",
"WayfinderModels",
"WayfinderRequests",
"WayfinderTokens",
"WayfinderSaved",
"WayfinderOffline",
"WayfinderDryRun",
"WayfinderMissingKeys",
"TrackingItem",
"MainWindowLiveUsageData",
"StartTrackingUsage",
Expand Down
11 changes: 11 additions & 0 deletions apps/desktop-tauri/src/lib/tauri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,17 @@ export function setProviderWorkspaceId(
return invoke<void>("set_provider_workspace_id", { providerId, workspaceId });
}

export function getProviderGatewayUrl(providerId: string): Promise<string | null> {
return invoke<string | null>("get_provider_gateway_url", { providerId });
}

export function setProviderGatewayUrl(
providerId: string,
gatewayUrl: string,
): Promise<void> {
return invoke<void>("set_provider_gateway_url", { providerId, gatewayUrl });
}

// ── Phase 6d — credential detection ──────────────────────────────────

export function openPath(path: string): Promise<void> {
Expand Down
Loading
Loading