fix(usage): don't resolve subscription windows for an absent auth signal

A missing per-turn authMode was mapped to "oauth", so an OpenAI api-key turn
that arrived without an explicit auth mechanism could resolve and display
ChatGPT subscription windows that aren't its own — served straight from the 60s
limits cache, which the "re-checked at fetch time" guard does not cover.

Treat a genuinely absent signal as non-eligible (same as api-key): no usage
provider resolves and the footer omits limit windows. Present mechanisms are
unchanged — oauth/auth-profile/token stay eligible, and only OpenAI is gated on
the credential type so other providers are unaffected. A real oauth/profile turn
always carries its mechanism; one arriving blank is an upstream tagging bug to
fix at the source.

Inverts the now-incorrect "absent => oauth-eligible" test into regression
coverage for the absent/api-key case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Peter Lindsey
2026-06-10 14:44:38 +08:00
committed by Ayaan Zaidi
parent 56f2102c28
commit 618d78144e
2 changed files with 19 additions and 10 deletions
+7 -7
View File
@@ -33,13 +33,13 @@ describe("getProviderUsageLimits credential awareness", () => {
expect(loadMock).toHaveBeenCalledTimes(1);
});
it("resolves OpenAI limits when the credential type is absent (oauth-eligible)", async () => {
loadMock.mockResolvedValue({
updatedAt: 0,
providers: [{ provider: "openai", displayName: "OpenAI", windows: [] }],
});
await getProviderUsageLimits("openai");
expect(loadMock).toHaveBeenCalledTimes(1);
it("returns undefined for an OpenAI turn with an absent credential type and never fetches", async () => {
// A missing authMode is not evidence of an oauth turn, so we must NOT resolve
// (and cache-borrow) ChatGPT windows for it — same outcome as an explicit
// api-key turn. A real oauth/profile turn carries its mechanism explicitly.
const out = await getProviderUsageLimits("openai");
expect(out).toBeUndefined();
expect(loadMock).not.toHaveBeenCalled();
});
it("resolves OpenAI limits for an oauth turn", async () => {
+12 -3
View File
@@ -21,10 +21,19 @@ const limitsCache = new Map<string, LimitsCacheEntry>();
// Map a per-turn auth signal — which may be the auth *mechanism* (e.g.
// "auth-profile"), not a credential *type* — to the usage-credential vocabulary
// resolveUsageProviderId expects. api-key/aws-sdk resolve NO usage provider, so an
// api-key turn never resolves "openai" and cannot borrow cached oauth windows;
// oauth/token/auth-profile and a missing signal are usage-eligible, and the actual
// credential is re-checked at fetch time before any usage request is made.
// api-key turn never resolves "openai" and cannot borrow cached oauth windows.
// A genuinely *absent* signal is treated the same way (non-eligible): a missing
// authMode is not evidence of an oauth/subscription turn, and resolving
// optimistically would let an untagged OpenAI api-key turn borrow cached
// oauth/ChatGPT windows (only OpenAI is gated on oauth/token here; other
// providers ignore the credential type, so they are unaffected). A real
// oauth/profile turn always carries its mechanism ("oauth"/"auth-profile"/
// "token"); one arriving blank is an upstream tagging bug to fix at the source,
// not a reason to show another turn's windows.
function toUsageCredentialType(raw: string | null | undefined): string {
if (raw == null) {
return "api-key";
}
if (raw === "api-key" || raw === "aws-sdk") {
return raw;
}