Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
548660a
Preserve Codex reset candidates across credit refresh
Finesssee Sep 6, 2026
04aa375
Port v0.56.2 cost history fixes
Finesssee Sep 6, 2026
439e54a
Honor agent metadata scan deadlines
Finesssee Sep 6, 2026
b4e408f
Keep v0.56.2 history dates readable
Finesssee Sep 6, 2026
5717bc9
Port v0.56.2 provider action semantics
Finesssee Sep 6, 2026
6730173
Match upstream Codex timestamp ordering
Finesssee Sep 6, 2026
b0f0e60
Preserve cost provenance across spend merges
Finesssee Sep 6, 2026
fc3cea2
Bound Codex cost catch-up work
Finesssee Sep 6, 2026
a401456
Port Codex fork parent accounting
Finesssee Sep 6, 2026
cfd8614
Preserve Codex reasoning token detail
Finesssee Sep 6, 2026
3e6788d
Expose native Codex reasoning tokens
Finesssee Sep 6, 2026
ea2dbd4
Preserve reasoning tokens across cache rebuilds
Finesssee Sep 6, 2026
af35242
Decompose spend contract tests
Finesssee Sep 6, 2026
1daefc6
Decompose cost scanner tests
Finesssee Sep 6, 2026
9c54ced
Decompose Codex cost scanner
Finesssee Sep 6, 2026
bb086b6
Decompose JSONL scanner tests
Finesssee Sep 6, 2026
72d359a
Decompose Codex JSONL scanner
Finesssee Sep 6, 2026
efb6232
Decompose Codex JSONL parser internals
Finesssee Sep 6, 2026
a5e8a4b
Fix 0.56.2 native Rust compile regressions
Finesssee Sep 7, 2026
6a17229
Fix 0.56.2 timestamp publication range
Finesssee Sep 7, 2026
be5e9ad
Stabilize 0.56.2 parallel native tests
Finesssee Sep 7, 2026
379dc31
Make 0.56.2 Rust port Clippy-clean
Finesssee Sep 7, 2026
c33e725
refactor: address 0.56.2 thermo review
Finesssee Sep 8, 2026
3b5851c
style: apply workspace rustfmt
Finesssee Sep 8, 2026
c5a41a2
fix: repair 0.56.2 review refactor
Finesssee Sep 8, 2026
11955aa
fix: remove stale pace re-export
Finesssee Sep 8, 2026
52c128c
Merge repaired 0.56.1 base
Finesssee Sep 8, 2026
97836a1
style: format repaired 0.56.2 stack
Finesssee Sep 8, 2026
321ad81
test: mark local OpenCode Go pace non-authoritative
Finesssee Sep 8, 2026
9c58915
Merge remote-tracking branch 'origin/main' into HEAD
Finesssee Sep 8, 2026
041f8b4
refactor: keep 0.56.2 modules below review threshold
Finesssee Sep 8, 2026
4b479aa
style: trim extracted weekly reset tests
Finesssee Sep 8, 2026
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
36 changes: 16 additions & 20 deletions apps/desktop-tauri/src-tauri/src/commands/bridge.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub(crate) mod pace;

use super::*;

// ── Bridge snapshot types ────────────────────────────────────────────
Expand Down Expand Up @@ -254,19 +256,6 @@ pub(crate) fn filter_hidden_codex_spark_rows(
}
}

pub(crate) fn pace_stage_str(stage: codexbar::core::PaceStage) -> &'static str {
use codexbar::core::PaceStage;
match stage {
PaceStage::OnTrack => "on_track",
PaceStage::SlightlyAhead => "slightly_ahead",
PaceStage::Ahead => "ahead",
PaceStage::FarAhead => "far_ahead",
PaceStage::SlightlyBehind => "slightly_behind",
PaceStage::Behind => "behind",
PaceStage::FarBehind => "far_behind",
}
}

impl ProviderUsageSnapshot {
pub(super) fn from_fetch_result(
id: ProviderId,
Expand All @@ -275,6 +264,7 @@ impl ProviderUsageSnapshot {
token_account_id: Option<uuid::Uuid>,
) -> Self {
let usage = &result.usage;
let allows_pace = result.pace_authoritative;

// A missing session is represented by an informational primary so the
// weekly lane keeps its canonical role. Use that weekly lane for the
Expand All @@ -284,11 +274,14 @@ impl ProviderUsageSnapshot {
} else {
Some(&usage.primary)
};
let primary_pace = primary_pace_window
.and_then(|window| codexbar::core::UsagePace::weekly(window, None, 10080));
let primary_pace = allows_pace.then(|| {
primary_pace_window
.and_then(|window| codexbar::core::UsagePace::weekly(window, None, 10080))
});
let primary_pace = primary_pace.flatten();

let pace = primary_pace.as_ref().map(|p| PaceSnapshot {
stage: pace_stage_str(p.stage).to_string(),
stage: pace::stage_str(p.stage).to_string(),
delta_percent: p.delta_percent,
will_last_to_reset: p.will_last_to_reset,
eta_seconds: p.eta_seconds,
Expand All @@ -297,10 +290,13 @@ impl ProviderUsageSnapshot {
});

// Compute pace for secondary window (weekly) to derive reserve info
let secondary_pace = usage
.secondary
.as_ref()
.and_then(|sw| codexbar::core::UsagePace::weekly(sw, None, 10080));
let secondary_pace = allows_pace.then(|| {
usage
.secondary
.as_ref()
.and_then(|sw| codexbar::core::UsagePace::weekly(sw, None, 10080))
});
let secondary_pace = secondary_pace.flatten();

let primary_snap = RateWindowSnapshot::from_rate_window(&usage.primary);

Expand Down
12 changes: 12 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/bridge/pace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pub(crate) fn stage_str(stage: codexbar::core::PaceStage) -> &'static str {
use codexbar::core::PaceStage;
match stage {
PaceStage::OnTrack => "on_track",
PaceStage::SlightlyAhead => "slightly_ahead",
PaceStage::Ahead => "ahead",
PaceStage::FarAhead => "far_ahead",
PaceStage::SlightlyBehind => "slightly_behind",
PaceStage::Behind => "behind",
PaceStage::FarBehind => "far_behind",
}
}
18 changes: 18 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,16 @@ fn dashboard_url_for_provider(provider_id: &str) -> Option<String> {
);
}

// OpenRouter's Usage Dashboard is the Activity page. Resolve it from the
// provider metadata before the legacy API-key catalog entry, which still
// points at the credits settings page.
if provider_id == ProviderId::OpenRouter.cli_name() {
return instantiate_provider(ProviderId::OpenRouter)
.metadata()
.dashboard_url
.map(|s| s.to_string());
}

if let Some(url) = codexbar::settings::get_api_key_providers()
.into_iter()
.find(|p| p.id.cli_name() == provider_id)
Expand Down Expand Up @@ -381,4 +391,12 @@ mod tests {
Some("https://chatgpt.com/codex/settings/usage")
);
}

#[test]
fn dashboard_url_resolves_openrouter_activity() {
assert_eq!(
dashboard_url_for_provider("openrouter").as_deref(),
Some("https://openrouter.ai/activity")
);
}
}
80 changes: 74 additions & 6 deletions apps/desktop-tauri/src-tauri/src/commands/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -771,18 +771,77 @@ fn provider_detail_roundtrips_through_serde() {
#[test]
fn pace_stage_serializes_to_snake_case_string() {
use codexbar::core::PaceStage;
assert_eq!(super::pace_stage_str(PaceStage::OnTrack), "on_track");
assert_eq!(
super::pace_stage_str(PaceStage::SlightlyAhead),
super::bridge::pace::stage_str(PaceStage::OnTrack),
"on_track"
);
assert_eq!(
super::bridge::pace::stage_str(PaceStage::SlightlyAhead),
"slightly_ahead"
);
assert_eq!(super::pace_stage_str(PaceStage::FarAhead), "far_ahead");
assert_eq!(
super::pace_stage_str(PaceStage::SlightlyBehind),
super::bridge::pace::stage_str(PaceStage::FarAhead),
"far_ahead"
);
assert_eq!(
super::bridge::pace::stage_str(PaceStage::SlightlyBehind),
"slightly_behind"
);
assert_eq!(super::pace_stage_str(PaceStage::Behind), "behind");
assert_eq!(super::pace_stage_str(PaceStage::FarBehind), "far_behind");
assert_eq!(super::bridge::pace::stage_str(PaceStage::Behind), "behind");
assert_eq!(
super::bridge::pace::stage_str(PaceStage::FarBehind),
"far_behind"
);
}

#[test]
fn local_opencodego_estimates_keep_quota_windows_but_drop_derived_pace() {
let now = chrono::Utc::now();
let usage = codexbar::core::UsageSnapshot::new(codexbar::core::RateWindow::with_details(
12.0,
Some(300),
Some(now + chrono::Duration::hours(2)),
None,
))
.with_secondary(codexbar::core::RateWindow::with_details(
23.0,
Some(10080),
Some(now + chrono::Duration::days(3)),
None,
))
.with_tertiary(codexbar::core::RateWindow::with_details(
34.0,
Some(43200),
Some(now + chrono::Duration::days(10)),
None,
));
let result = ProviderFetchResult::new(
usage,
codexbar::providers::opencodego::LOCAL_ESTIMATE_SOURCE_LABEL,
)
.with_non_authoritative_pace();
let metadata = instantiate_provider(ProviderId::OpenCodeGo)
.metadata()
.clone();
let snapshot =
ProviderUsageSnapshot::from_fetch_result(ProviderId::OpenCodeGo, &metadata, &result, None);

assert_eq!(snapshot.source_label, "local estimate");
assert_eq!(snapshot.primary.used_percent, 12.0);
assert_eq!(snapshot.secondary.as_ref().unwrap().used_percent, 23.0);
assert_eq!(snapshot.tertiary.as_ref().unwrap().used_percent, 34.0);
assert!(snapshot.primary.resets_at.is_some());
assert!(snapshot.secondary.as_ref().unwrap().resets_at.is_some());
assert!(snapshot.tertiary.as_ref().unwrap().resets_at.is_some());
assert!(snapshot.pace.is_none());
assert!(
snapshot
.secondary
.as_ref()
.unwrap()
.reserve_percent
.is_none()
);
}

#[test]
Expand Down Expand Up @@ -862,6 +921,7 @@ fn provider_cache_upsert_replaces_existing_provider() {
cost: None,
wayfinder_usage: None,
source_label: "CLI".to_string(),
pace_authoritative: true,
};
let mut first =
ProviderUsageSnapshot::from_fetch_result(ProviderId::Codex, &metadata, &result, None);
Expand All @@ -885,6 +945,7 @@ fn provider_cache_prunes_disabled_providers() {
cost: None,
wayfinder_usage: None,
source_label: "CLI".to_string(),
pace_authoritative: true,
};
let codex =
ProviderUsageSnapshot::from_fetch_result(ProviderId::Codex, &metadata, &result, None);
Expand Down Expand Up @@ -915,6 +976,7 @@ fn hiding_codex_spark_rows_preserves_other_extra_usage() {
cost: None,
wayfinder_usage: None,
source_label: "CLI".to_string(),
pace_authoritative: true,
};
let mut snapshot =
ProviderUsageSnapshot::from_fetch_result(ProviderId::Codex, &metadata, &result, None);
Expand Down Expand Up @@ -945,6 +1007,7 @@ fn claude_transient_auth_failure_preserves_first_last_good_snapshot() {
cost: None,
wayfinder_usage: None,
source_label: "OAuth".to_string(),
pace_authoritative: true,
};
let good =
ProviderUsageSnapshot::from_fetch_result(ProviderId::Claude, &metadata, &result, None);
Expand Down Expand Up @@ -975,6 +1038,7 @@ fn claude_repeated_auth_failure_surfaces_error() {
cost: None,
wayfinder_usage: None,
source_label: "OAuth".to_string(),
pace_authoritative: true,
};
let good =
ProviderUsageSnapshot::from_fetch_result(ProviderId::Claude, &metadata, &result, None);
Expand Down Expand Up @@ -1010,6 +1074,7 @@ fn claude_cli_parse_failure_keeps_last_good_every_time() {
cost: None,
wayfinder_usage: None,
source_label: "CLI".to_string(),
pace_authoritative: true,
};
let good =
ProviderUsageSnapshot::from_fetch_result(ProviderId::Claude, &metadata, &result, None);
Expand Down Expand Up @@ -1045,6 +1110,7 @@ fn claude_hard_credentials_missing_does_not_preserve_stale() {
cost: None,
wayfinder_usage: None,
source_label: "OAuth".to_string(),
pace_authoritative: true,
};
let good =
ProviderUsageSnapshot::from_fetch_result(ProviderId::Claude, &metadata, &result, None);
Expand Down Expand Up @@ -1195,6 +1261,7 @@ fn japanese_provider_snapshot_localizes_weekly_label() {
cost: None,
wayfinder_usage: None,
source_label: "OAuth".to_string(),
pace_authoritative: true,
};

let snapshot =
Expand Down Expand Up @@ -1224,6 +1291,7 @@ fn japanese_provider_snapshot_localizes_pace_reserve_description() {
cost: None,
wayfinder_usage: None,
source_label: "OAuth".to_string(),
pace_authoritative: true,
};

let snapshot =
Expand Down
35 changes: 35 additions & 0 deletions apps/desktop-tauri/src/components/MenuCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,41 @@ describe("MenuCard", () => {
expect(container.querySelector(".menu-metric__forecast")).not.toBeInTheDocument();
});

it("hides derived pace advice for local OpenCode Go estimates", async () => {
const resetAt = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000);
const snapshot = provider(null, 12);
snapshot.providerId = "opencodego";
snapshot.displayName = "OpenCode Go";
snapshot.sourceLabel = "local estimate";
snapshot.primary = rateWindow(12, {
windowMinutes: 5 * 60,
resetsAt: new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString(),
});
snapshot.secondary = rateWindow(23, {
windowMinutes: 7 * 24 * 60,
resetsAt: resetAt.toISOString(),
reservePercent: 34,
reserveWillLastToReset: true,
});
snapshot.pace = {
stage: "far_ahead",
deltaPercent: 20,
expectedUsedPercent: 20,
actualUsedPercent: 40,
etaSeconds: 90 * 60,
willLastToReset: false,
};

const { container } = renderCard(snapshot);

expect(await screen.findByText("88% left")).toBeInTheDocument();
expect(screen.getByText("77% left")).toBeInTheDocument();
expect(container.querySelector(".menu-card__pace")).not.toBeInTheDocument();
expect(screen.queryByText("On-pace budget")).not.toBeInTheDocument();
expect(screen.queryByText(/in reserve/)).not.toBeInTheDocument();
expect(container.querySelector(".menu-metric__forecast")).not.toBeInTheDocument();
});

it("renders local token and cost totals after chart data loads", async () => {
const { container } = renderCard(provider(null));

Expand Down
14 changes: 11 additions & 3 deletions apps/desktop-tauri/src/components/MenuCardDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
SessionEquivalentForecastSnapshot,
} from "../types/bridge";
import { useLocale } from "../hooks/useLocale";
import { providerAllowsPace } from "../lib/providerPace";
import {
useFormattedResetTime,
type ResetTimeFormatMode,
Expand Down Expand Up @@ -458,7 +459,10 @@ export function describeCard(
const hasCost =
!!provider.cost &&
(costSummaryDisplayStyle !== "hidden" || provider.cost.alwaysVisible === true);
const hasPace = showPace && !!provider.pace;
const hasPace =
showPace &&
providerAllowsPace(provider.providerId, provider.sourceLabel) &&
!!provider.pace;
const hasDetails =
!provider.error &&
(hasMetrics || hasCost || hasPace || hasCharts || !!localUsage || !!wayfinderUsage);
Expand Down Expand Up @@ -486,6 +490,10 @@ export default function MenuCardDetails({
onLayoutChange,
}: MenuCardDetailsProps) {
const { t } = useLocale();
const paceEnabled =
display.showPace !== false &&
providerAllowsPace(provider.providerId, provider.sourceLabel);
const metricDisplay = paceEnabled ? display : { ...display, showPace: false };
const [expandedPaceWindow, setExpandedPaceWindow] = useState<string | null>(null);
const formattedCostReset = useFormattedResetTime(
provider.cost?.resetsAt ?? null,
Expand Down Expand Up @@ -517,7 +525,7 @@ export default function MenuCardDetails({
title={m.label}
snap={m.snap}
exhaustedLabel={t("DetailWindowExhausted")}
display={display}
display={metricDisplay}
expanded={expandedPaceWindow === m.id}
resetFormatMode={m.resetFormatMode}
sessionEquivalentForecast={m.sessionEquivalentForecast}
Expand Down Expand Up @@ -620,7 +628,7 @@ export default function MenuCardDetails({

{(hasMetrics || hasCost) && hasPace && <div className="menu-card__divider" />}

{hasPace && provider.pace && (
{paceEnabled && hasPace && provider.pace && (
<section className="menu-card__group menu-card__pace">
<div className="menu-card__pace-header">
<span className="menu-card__group-title">{t("DetailPaceTitle")}</span>
Expand Down
Loading