diff --git a/src/agents/tools/session-status-tool.ts b/src/agents/tools/session-status-tool.ts index 86cf5ad0f..c5df85f59 100644 --- a/src/agents/tools/session-status-tool.ts +++ b/src/agents/tools/session-status-tool.ts @@ -311,6 +311,7 @@ export function createSessionStatusTool(opts?: { const formatted = formatUsageWindowSummary(snapshot, { now: Date.now(), maxWindows: 2, + includeResets: true, }); if (formatted) usageByProvider.set(snapshot.provider, formatted); } diff --git a/src/auto-reply/reply/commands-status.ts b/src/auto-reply/reply/commands-status.ts index 6516df199..382b0768e 100644 --- a/src/auto-reply/reply/commands-status.ts +++ b/src/auto-reply/reply/commands-status.ts @@ -168,6 +168,7 @@ export async function buildStatusReply(params: { const formatted = formatUsageWindowSummary(snapshot, { now: Date.now(), maxWindows: 2, + includeResets: true, }); if (formatted) usageByProvider.set(snapshot.provider, formatted); } diff --git a/src/commands/models/list.status-command.ts b/src/commands/models/list.status-command.ts index 3b1d2f860..ed84930a9 100644 --- a/src/commands/models/list.status-command.ts +++ b/src/commands/models/list.status-command.ts @@ -424,7 +424,11 @@ export async function modelsStatusCommand( timeoutMs: 3500, }); for (const snapshot of usageSummary.providers) { - const formatted = formatUsageWindowSummary(snapshot, { now: Date.now(), maxWindows: 2 }); + const formatted = formatUsageWindowSummary(snapshot, { + now: Date.now(), + maxWindows: 2, + includeResets: true, + }); if (formatted) { usageByProvider.set(snapshot.provider, formatted); } diff --git a/src/infra/provider-usage.fetch.minimax.ts b/src/infra/provider-usage.fetch.minimax.ts index 9c194ebb2..071b1580a 100644 --- a/src/infra/provider-usage.fetch.minimax.ts +++ b/src/infra/provider-usage.fetch.minimax.ts @@ -249,21 +249,32 @@ function deriveWindowLabel(payload: Record): string { } function deriveUsedPercent(payload: Record): number | null { - const percentRaw = pickNumber(payload, PERCENT_KEYS); - if (percentRaw !== undefined) { - const normalized = percentRaw <= 1 ? percentRaw * 100 : percentRaw; - return clampPercent(normalized); + const total = pickNumber(payload, TOTAL_KEYS); + let used = pickNumber(payload, USED_KEYS); + const remaining = pickNumber(payload, REMAINING_KEYS); + if (used === undefined && remaining !== undefined && total !== undefined) { + used = total - remaining; } - const total = pickNumber(payload, TOTAL_KEYS); - if (!total || total <= 0) return null; - let used = pickNumber(payload, USED_KEYS); - if (used === undefined) { - const remaining = pickNumber(payload, REMAINING_KEYS); - if (remaining !== undefined) used = total - remaining; + const fromCounts = + total && total > 0 && used !== undefined && Number.isFinite(used) + ? clampPercent((used / total) * 100) + : null; + + const percentRaw = pickNumber(payload, PERCENT_KEYS); + if (percentRaw !== undefined) { + const normalized = clampPercent(percentRaw <= 1 ? percentRaw * 100 : percentRaw); + if (fromCounts !== null) { + const inverted = clampPercent(100 - normalized); + if (Math.abs(normalized - fromCounts) <= 1 || Math.abs(inverted - fromCounts) <= 1) { + return fromCounts; + } + return fromCounts; + } + return normalized; } - if (used === undefined || !Number.isFinite(used)) return null; - return clampPercent((used / total) * 100); + + return fromCounts; } export async function fetchMinimaxUsage( diff --git a/src/infra/provider-usage.test.ts b/src/infra/provider-usage.test.ts index 8293293f5..d93135ec9 100644 --- a/src/infra/provider-usage.test.ts +++ b/src/infra/provider-usage.test.ts @@ -175,6 +175,41 @@ describe("provider usage loading", () => { expect(mockFetch).toHaveBeenCalled(); }); + it("prefers MiniMax count-based usage when percent looks inverted", async () => { + const makeResponse = (status: number, body: unknown): Response => { + const payload = typeof body === "string" ? body : JSON.stringify(body); + const headers = typeof body === "string" ? undefined : { "Content-Type": "application/json" }; + return new Response(payload, { status, headers }); + }; + + const mockFetch = vi.fn, ReturnType>(async (input) => { + const url = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + if (url.includes("api.minimax.io/v1/coding_plan/remains")) { + return makeResponse(200, { + base_resp: { status_code: 0, status_msg: "ok" }, + data: { + prompt_limit: 200, + prompt_remain: 150, + usage_percent: 75, + next_reset_time: "2026-01-07T05:00:00Z", + }, + }); + } + return makeResponse(404, "not found"); + }); + + const summary = await loadProviderUsageSummary({ + now: Date.UTC(2026, 0, 7, 0, 0, 0), + auth: [{ provider: "minimax", token: "token-1b" }], + fetch: mockFetch, + }); + + const minimax = summary.providers.find((p) => p.provider === "minimax"); + expect(minimax?.windows[0]?.usedPercent).toBe(25); + expect(mockFetch).toHaveBeenCalled(); + }); + it("handles MiniMax model_remains usage payloads", async () => { const makeResponse = (status: number, body: unknown): Response => { const payload = typeof body === "string" ? body : JSON.stringify(body);