diff --git a/open-sse/services/usage.ts b/open-sse/services/usage.ts index 8805ff9947..3122403277 100644 --- a/open-sse/services/usage.ts +++ b/open-sse/services/usage.ts @@ -206,6 +206,16 @@ function pickMiniMaxRepresentativeModel( return pool.reduce((best, current) => (getTotal(current) > getTotal(best) ? current : best)); } +function createMiniMaxQuotaFromCount( + total: number, + count: number, + resetAt: string | null, + countMeansRemaining: boolean +): UsageQuota { + const used = countMeansRemaining ? Math.max(total - count, 0) : count; + return createQuotaFromUsage(used, total, resetAt); +} + function getMiniMaxAuthErrorMessage(message: string): string { const normalized = message.toLowerCase(); if ( @@ -319,11 +329,12 @@ async function getMiniMaxUsage(apiKey: string, provider: "minimax" | "minimax-cn return { message: "MiniMax connected. No text quota data was returned." }; } + const countMeansRemaining = usageUrl.includes("/coding_plan/remains"); const quotas: Record = {}; const sessionModel = pickMiniMaxRepresentativeModel(textModels, getMiniMaxSessionTotal); if (sessionModel) { const total = getMiniMaxSessionTotal(sessionModel); - const remain = Math.max( + const count = Math.max( 0, toNumber( getFieldValue( @@ -334,9 +345,9 @@ async function getMiniMaxUsage(apiKey: string, provider: "minimax" | "minimax-cn 0 ) ); - quotas["session (5h)"] = createQuotaFromUsage( - Math.max(total - remain, 0), + quotas["session (5h)"] = createMiniMaxQuotaFromCount( total, + count, getMiniMaxQuotaResetAt( sessionModel, capturedAtMs, @@ -344,23 +355,24 @@ async function getMiniMaxUsage(apiKey: string, provider: "minimax" | "minimax-cn "remainsTime", "end_time", "endTime" - ) + ), + countMeansRemaining ); } const weeklyModel = pickMiniMaxRepresentativeModel(textModels, getMiniMaxWeeklyTotal); if (weeklyModel && getMiniMaxWeeklyTotal(weeklyModel) > 0) { const total = getMiniMaxWeeklyTotal(weeklyModel); - const remain = Math.max( + const count = Math.max( 0, toNumber( getFieldValue(weeklyModel, "current_weekly_usage_count", "currentWeeklyUsageCount"), 0 ) ); - quotas["weekly (7d)"] = createQuotaFromUsage( - Math.max(total - remain, 0), + quotas["weekly (7d)"] = createMiniMaxQuotaFromCount( total, + count, getMiniMaxQuotaResetAt( weeklyModel, capturedAtMs, @@ -368,7 +380,8 @@ async function getMiniMaxUsage(apiKey: string, provider: "minimax" | "minimax-cn "weeklyRemainsTime", "weekly_end_time", "weeklyEndTime" - ) + ), + countMeansRemaining ); } diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx index 389f21fbe8..7e77aa83b8 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx @@ -611,9 +611,10 @@ export default function ProviderLimits() {
{quota.message}
) : quota?.quotas?.length > 0 ? ( quota.quotas.map((q, i) => { - const remainingPercentage = q.unlimited + const remainingPercentageRaw = q.unlimited ? 100 : (q.remainingPercentage ?? calculatePercentage(q.used, q.total)); + const remainingPercentage = Math.round(remainingPercentageRaw); const colors = getBarColor(remainingPercentage); const cd = formatCountdown(q.resetAt); const shortName = formatQuotaLabel(q.name); diff --git a/tests/unit/usage-service-hardening.test.ts b/tests/unit/usage-service-hardening.test.ts index bd0fe9e3f0..fd28dfc780 100644 --- a/tests/unit/usage-service-hardening.test.ts +++ b/tests/unit/usage-service-hardening.test.ts @@ -945,6 +945,46 @@ test("usage service covers MiniMax usage parsing, documented endpoint fallback a assert.match(invalid.message, /Token Plan API key/i); }); +test("usage service treats MiniMax token-plan counts as used usage", async () => { + const beforeCall = Date.now(); + + globalThis.fetch = async (url, init = {}) => { + assert.equal(String(url), "https://www.minimax.io/v1/token_plan/remains"); + assert.equal((init as any).headers.Authorization, "Bearer minimax-key"); + + return new Response( + JSON.stringify({ + base_resp: { status_code: 0, status_msg: "ok" }, + model_remains: [ + { + model_name: "MiniMax-M2.7", + remains_time: 300_000, + current_interval_total_count: 15000, + current_interval_usage_count: 13, + current_weekly_total_count: 150000, + current_weekly_usage_count: 66, + weekly_remains_time: 604_800_000, + }, + ], + }), + { status: 200 } + ); + }; + + const usage: any = await usageService.getUsageForProvider({ + provider: "minimax", + apiKey: "minimax-key", + }); + + assert.equal(usage.quotas["session (5h)"].used, 13); + assert.equal(usage.quotas["session (5h)"].remaining, 14987); + assert.equal(usage.quotas["session (5h)"].remainingPercentage, 99.91333333333333); + assert.equal(usage.quotas["weekly (7d)"].used, 66); + assert.equal(usage.quotas["weekly (7d)"].remaining, 149934); + assert.equal(usage.quotas["weekly (7d)"].remainingPercentage, 99.956); + assert.ok(Date.parse(usage.quotas["session (5h)"].resetAt) >= beforeCall + 240_000); +}); + test("usage service parses Cursor team quotas and clamps on-demand ratio", async () => { const calls: any[] = []; globalThis.fetch = async (url, init = {}) => {