diff --git a/CHANGELOG.md b/CHANGELOG.md index 00c715a..3678f23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to LUOYE Codex Meter are documented here. +## [0.3.5] - 2026-07-13 + +### Fixed + +- Identify five-hour and weekly quota windows by their declared duration instead of their response position +- Leave the five-hour value and reset time blank when Codex only provides the weekly quota window +- Preserve compatibility with older app-server responses that omit window duration metadata + ## [0.3.4] - 2026-07-12 Initial public release. diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs index 57dea1c..413b322 100644 --- a/src/AssemblyInfo.cs +++ b/src/AssemblyInfo.cs @@ -8,5 +8,5 @@ [assembly: AssemblyCopyright("Copyright (c) 2026 LUOYE")] [assembly: ComVisible(false)] [assembly: Guid("c2b06a3d-877f-43b1-a2c2-7d9c5585f84f")] -[assembly: AssemblyVersion("0.3.4.0")] -[assembly: AssemblyFileVersion("0.3.4.0")] +[assembly: AssemblyVersion("0.3.5.0")] +[assembly: AssemblyFileVersion("0.3.5.0")] diff --git a/src/MeterWindow.cs b/src/MeterWindow.cs index 292685a..fd5efb0 100644 --- a/src/MeterWindow.cs +++ b/src/MeterWindow.cs @@ -1355,13 +1355,15 @@ private void ApplySnapshot(QuotaSnapshot snapshot) fiveHourDetail, snapshot.FiveHourRemaining, snapshot.FiveHourReset, - "5 小时额度"); + "5 小时额度", + true); SetMetric( weeklyMetric, weeklyDetail, snapshot.WeeklyRemaining, snapshot.WeeklyReset, - "本周额度"); + "本周额度", + false); resetCountText.Text = FormatCompactCount(snapshot.ResetCredits); resetCountText.ToolTip = snapshot.ResetCredits + " 次重置额度"; detailResetCountText.Text = snapshot.ResetCredits + " 次重置额度"; @@ -1374,16 +1376,21 @@ private void SetMetric( DetailMetricElements detail, int? remaining, DateTime? resetAt, - string label) + string label, + bool emptyWhenUnavailable = false) { metric.Remaining = remaining; detail.Remaining = remaining; - metric.Value.Text = remaining.HasValue ? remaining.Value + "%" : "--"; + metric.Value.Text = remaining.HasValue + ? remaining.Value + "%" + : emptyWhenUnavailable ? String.Empty : "--"; detail.Value.Text = metric.Value.Text; - detail.Reset.Text = FormatResetTime(resetAt); + detail.Reset.Text = emptyWhenUnavailable && !remaining.HasValue + ? String.Empty + : FormatResetTime(resetAt); var tooltip = remaining.HasValue ? label + ":剩余 " + remaining.Value + "%," + FormatResetTime(resetAt) - : label + "暂不可用"; + : emptyWhenUnavailable ? label + ":当前未提供" : label + "暂不可用"; metric.Root.ToolTip = tooltip; detail.Root.ToolTip = tooltip; diff --git a/src/QuotaService.cs b/src/QuotaService.cs index c04038e..44e4c63 100644 --- a/src/QuotaService.cs +++ b/src/QuotaService.cs @@ -41,7 +41,7 @@ internal async Task ReadAsync() process.BeginErrorReadLine(); await process.StandardInput.WriteLineAsync( - "{\"id\":1,\"method\":\"initialize\",\"params\":{\"clientInfo\":{\"name\":\"luoye-codex-meter\",\"version\":\"0.3.4\"}}}"); + "{\"id\":1,\"method\":\"initialize\",\"params\":{\"clientInfo\":{\"name\":\"luoye-codex-meter\",\"version\":\"0.3.5\"}}}"); await process.StandardInput.WriteLineAsync("{\"method\":\"initialized\"}"); await process.StandardInput.WriteLineAsync( "{\"id\":2,\"method\":\"account/rateLimits/read\",\"params\":null}"); @@ -328,18 +328,53 @@ private QuotaSnapshot ParseSnapshot(Dictionary result) var primary = GetDictionary(limits, "primary"); var secondary = GetDictionary(limits, "secondary"); + var fiveHour = FindWindow(primary, secondary, 5 * 60, "primary"); + var weekly = FindWindow(primary, secondary, 7 * 24 * 60, "secondary"); var credits = GetDictionary(result, "rateLimitResetCredits"); return new QuotaSnapshot { - FiveHourRemaining = RemainingPercent(primary), - WeeklyRemaining = RemainingPercent(secondary), - FiveHourReset = ReadUnixTime(primary, "resetsAt"), - WeeklyReset = ReadUnixTime(secondary, "resetsAt"), + FiveHourRemaining = RemainingPercent(fiveHour), + WeeklyRemaining = RemainingPercent(weekly), + FiveHourReset = ReadUnixTime(fiveHour, "resetsAt"), + WeeklyReset = ReadUnixTime(weekly, "resetsAt"), ResetCredits = ReadInt(credits, "availableCount") ?? 0 }; } + private static Dictionary FindWindow( + Dictionary primary, + Dictionary secondary, + double expectedDurationMinutes, + string legacyPosition) + { + if (HasDuration(primary, expectedDurationMinutes)) + { + return primary; + } + if (HasDuration(secondary, expectedDurationMinutes)) + { + return secondary; + } + + // Older app-server responses did not identify window durations. Keep + // their positional primary/secondary mapping as a compatibility fallback. + var legacyWindow = String.Equals(legacyPosition, "primary", StringComparison.Ordinal) + ? primary + : secondary; + return ReadDouble(legacyWindow, "windowDurationMins").HasValue + ? null + : legacyWindow; + } + + private static bool HasDuration( + Dictionary window, + double expectedDurationMinutes) + { + var actual = ReadDouble(window, "windowDurationMins"); + return actual.HasValue && Math.Abs(actual.Value - expectedDurationMinutes) < 0.5; + } + private static bool HasResponseId(Dictionary message, int expected) { object raw;