From 225c4374770b68cb618984bb2b8a81a5c498123d Mon Sep 17 00:00:00 2001 From: iceteaSA <171169159+iceteaSA@users.noreply.github.com> Date: Fri, 4 Sep 2026 16:22:52 +0200 Subject: [PATCH] fix(reset): keep unknown credit counts distinct from zero Preserve omitted WHAM applicability counts for account display and disclosure. Derive missing available counts from eligible credits, render unknown values consistently in command and TUI confirmations, and update coverage for omitted fields while removing the duplicate precondition test. --- packages/opencode/src/commands.ts | 12 ++- packages/opencode/src/core/reset-credits.ts | 21 +++-- packages/opencode/src/tests/commands.test.ts | 87 +++++++++++++++++++ .../opencode/src/tests/reset-credits.test.ts | 6 -- packages/opencode/src/tui/command-dialogs.tsx | 4 +- 5 files changed, 112 insertions(+), 18 deletions(-) diff --git a/packages/opencode/src/commands.ts b/packages/opencode/src/commands.ts index 1c84140..0609f15 100644 --- a/packages/opencode/src/commands.ts +++ b/packages/opencode/src/commands.ts @@ -16,6 +16,7 @@ import { whamUsageFn } from './core/provider' import type { QuotaManager } from './core/quota-manager' import type { RefreshAllQuotaResult } from './core/refresh-all-quota' import { + countEligibleResetCredits, evaluateResetPrecondition, listResetCredits, ResetCreditError, @@ -1235,9 +1236,12 @@ async function buildResetPreviewRow( listResetCredits(ctx.fetchImpl, target.accessToken, wireAccountId), ]) const selectedCredit = selectCreditToSpend(credits.credits) + const eligibleCreditCount = countEligibleResetCredits(credits.credits) const availableCount = - credits.availableCount ?? quota.resetCreditsAvailable ?? 0 - const applicableAvailableCount = quota.resetCreditsApplicable ?? 0 + credits.availableCount ?? + quota.resetCreditsAvailable ?? + (eligibleCreditCount > 0 ? eligibleCreditCount : undefined) + const applicableAvailableCount = quota.resetCreditsApplicable const precondition = evaluateResetPrecondition( quota, ctx.quotaManager.isRateLimited(accountKey), @@ -1293,7 +1297,7 @@ function renderResetAccountList(rows: readonly ResetPreviewRow[]): string { const credits = row.availableCount === undefined ? 'credits unavailable' - : `${row.applicableAvailableCount ?? 0}/${row.availableCount} applicable/available` + : `${row.applicableAvailableCount === undefined ? '?' : row.applicableAvailableCount}/${row.availableCount} applicable/available` const status = row.eligible ? `eligible ยท credit ${row.selectedCreditId} expires ${row.selectedCreditExpiresAt}` : row.reason @@ -1312,7 +1316,7 @@ function renderResetConfirm(row: ResetPreviewRow): string { '', `Account: **${row.label}** (\`${row.accountKey}\`)`, `Current quota: **${row.usedPercent ?? 'unknown'}% used**`, - `Credit: **Spend 1 of ${row.availableCount ?? 0}**`, + `Credit: **Spend 1 of ${row.availableCount ?? 'unknown'}**`, `Credit expires: **${row.selectedCreditExpiresAt ?? 'unavailable'}**`, `Quota resets: **${row.resetTime ?? 'unavailable'}**`, '', diff --git a/packages/opencode/src/core/reset-credits.ts b/packages/opencode/src/core/reset-credits.ts index 4de92dc..623a20d 100644 --- a/packages/opencode/src/core/reset-credits.ts +++ b/packages/opencode/src/core/reset-credits.ts @@ -571,17 +571,26 @@ export function selectCreditToSpend( credits: readonly ResetCredit[], ): ResetCredit | undefined { return [...credits] - .filter( - (credit) => - credit.status === 'available' && - credit.isSupportedByPlan && - credit.resetType === 'codex_rate_limits', - ) + .filter(isResetCreditEligible) .sort( (left, right) => Date.parse(left.expiresAt) - Date.parse(right.expiresAt), )[0] } +export function isResetCreditEligible(credit: ResetCredit): boolean { + return ( + credit.status === 'available' && + credit.isSupportedByPlan && + credit.resetType === 'codex_rate_limits' + ) +} + +export function countEligibleResetCredits( + credits: readonly ResetCredit[], +): number { + return credits.filter(isResetCreditEligible).length +} + function isTerminalConsumeKind(value: unknown): value is ResetConsumeKind { return ( value === 'reset' || diff --git a/packages/opencode/src/tests/commands.test.ts b/packages/opencode/src/tests/commands.test.ts index a69089f..48195c9 100644 --- a/packages/opencode/src/tests/commands.test.ts +++ b/packages/opencode/src/tests/commands.test.ts @@ -2415,6 +2415,93 @@ describe('commands', () => { ) }) + test('omitted applicable count stays unknown in the account preview', async () => { + await saveResetAccounts([]) + const { ctx } = await makeResetCommandHarness( + configPath, + now, + resetFixture(), + ) + ctx.fetchImpl = fetchStub(async (input) => { + if (input.toString().endsWith('/wham/usage')) { + return Response.json({ + rate_limit: { + primary_window: { + used_percent: 100, + reset_at: '2026-07-18T00:00:00.000Z', + }, + }, + rate_limit_reset_credits: { available_count: 1 }, + }) + } + return Response.json({ + credits: [ + { + id: 'credit-omitted-applicable', + status: 'available', + expires_at: '2026-08-01T00:00:00.000Z', + reset_type: 'codex_rate_limits', + is_supported_by_plan: true, + }, + ], + }) + }) + + const payload = await buildDialogPayload('openai-reset', '', ctx) + const rows = payload.knobs.accounts as Array> + + expect(rows).toContainEqual( + expect.objectContaining({ + accountKey: 'main', + availableCount: 1, + applicableAvailableCount: undefined, + eligible: true, + }), + ) + expect(payload.text).toContain('?/1 applicable/available') + expect(payload.text).not.toContain('does not currently count') + }) + + test('omitted credit counts fall back to the eligible credit total', async () => { + await saveResetAccounts([]) + const { ctx } = await makeResetCommandHarness( + configPath, + now, + resetFixture(), + ) + ctx.fetchImpl = fetchStub(async (input) => { + if (input.toString().endsWith('/wham/usage')) { + return Response.json({ + rate_limit: { + primary_window: { + used_percent: 100, + reset_at: '2026-07-18T00:00:00.000Z', + }, + }, + }) + } + return Response.json({ + credits: [ + { + id: 'credit-omitted-counts', + status: 'available', + expires_at: '2026-08-01T00:00:00.000Z', + reset_type: 'codex_rate_limits', + is_supported_by_plan: true, + }, + ], + }) + }) + + const payload = await buildDialogPayload( + 'openai-reset', + 'select main', + ctx, + ) + + expect(payload.text).toContain('Spend 1 of 1') + }) + test('account preview keeps per-account failures visible and requires a stable identity for action', async () => { await saveResetAccounts([ makeAccount('broken', { diff --git a/packages/opencode/src/tests/reset-credits.test.ts b/packages/opencode/src/tests/reset-credits.test.ts index 180f5f2..dfdb61b 100644 --- a/packages/opencode/src/tests/reset-credits.test.ts +++ b/packages/opencode/src/tests/reset-credits.test.ts @@ -458,12 +458,6 @@ describe('reset redemption precondition', () => { ).toEqual({ ok: false, reason: 'not exhausted' }) }) - it('accepts exhausted quota when the server reports no applicable credits', () => { - expect( - evaluateResetPrecondition({ primary: quotaWindow(100) }, false, now), - ).toEqual({ ok: true }) - }) - it('treats a 100%-used expired window as stale rather than exhausted', () => { expect( evaluateResetPrecondition( diff --git a/packages/opencode/src/tui/command-dialogs.tsx b/packages/opencode/src/tui/command-dialogs.tsx index 06b2276..6a7a015 100644 --- a/packages/opencode/src/tui/command-dialogs.tsx +++ b/packages/opencode/src/tui/command-dialogs.tsx @@ -142,7 +142,7 @@ function resetAccountOptions(payload: OpenDialogPayload): ResetDialogOption[] { account.usedPercent === undefined ? 'quota unavailable' : `${account.usedPercent}%` - const counts = `${account.applicableAvailableCount ?? 0}/${account.availableCount ?? 0}` + const counts = `${account.applicableAvailableCount === undefined ? '?' : account.applicableAvailableCount}/${account.availableCount ?? '?'}` const status = account.eligible ? 'eligible' : (account.reason ?? 'unavailable') @@ -278,7 +278,7 @@ function openResetDialog( const preview = state.knobs.preview as ResetPreviewKnob | undefined const accountKey = preview?.accountKey const chatgptAccountId = preview?.chatgptAccountId - const availableCount = preview?.availableCount ?? 0 + const availableCount = preview?.availableCount ?? 'unknown' const DialogConfirm = api.ui.DialogConfirm api.ui.dialog.replace(() => (