fix(dashboard): resolve Unknown plan display in Provider Limits

- Replace || "Unknown" fallbacks with || null in usage.ts (GLM + Claude legacy)
- Add plan extraction to Claude OAuth mapTokens (account_tier > plan > subscription_type > billing.plan)
- Add unit tests for plan extraction and Provider Limits badge resolution
This commit is contained in:
congvc
2026-05-06 22:25:55 +07:00
parent 08e18867fd
commit 8a9d0d3504
4 changed files with 93 additions and 11 deletions

View File

@@ -554,9 +554,7 @@ async function getGlmUsage(apiKey: string, providerSpecificData?: Record<string,
}
const levelRaw = typeof data.level === "string" ? data.level : "";
const plan = levelRaw
? levelRaw.charAt(0).toUpperCase() + levelRaw.slice(1).toLowerCase()
: "Unknown";
const plan = levelRaw ? levelRaw.charAt(0).toUpperCase() + levelRaw.slice(1).toLowerCase() : null;
return { plan, quotas };
}
@@ -1820,7 +1818,7 @@ async function getClaudeUsageLegacy(accessToken) {
if (usageResponse.ok) {
const usage = await usageResponse.json();
return {
plan: settings.plan || "Unknown",
plan: settings.plan || null,
organization: settings.organization_name,
quotas: usage,
};
@@ -1828,7 +1826,7 @@ async function getClaudeUsageLegacy(accessToken) {
}
return {
plan: settings.plan || "Unknown",
plan: settings.plan || null,
organization: settings.organization_name,
message: "Claude connected. Usage details require admin access.",
};

View File

@@ -1,5 +1,43 @@
import { CLAUDE_CONFIG } from "../constants/oauth";
function toRecord(value: unknown): Record<string, unknown> {
return value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
}
function firstNonEmptyString(...values: unknown[]): string | undefined {
for (const value of values) {
if (typeof value !== "string") {
continue;
}
const trimmed = value.trim();
if (trimmed) {
return trimmed;
}
}
return undefined;
}
function extractPlanFromPayload(payload: unknown): string | undefined {
const data = toRecord(payload);
const billing = toRecord(data.billing);
return firstNonEmptyString(data.account_tier, data.plan, data.subscription_type, billing.plan);
}
function extractClaudePlan(tokens: unknown, extra: unknown): string | undefined {
const extraData = toRecord(extra);
return firstNonEmptyString(
extractPlanFromPayload(tokens),
extractPlanFromPayload(extraData.userInfo),
extractPlanFromPayload(extra)
);
}
export const claude = {
config: CLAUDE_CONFIG,
flowType: "authorization_code_pkce",
@@ -48,10 +86,15 @@ export const claude = {
return await response.json();
},
mapTokens: (tokens) => ({
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expires_in,
scope: tokens.scope,
}),
mapTokens: (tokens, extra) => {
const plan = extractClaudePlan(tokens, extra);
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expires_in,
scope: tokens.scope,
providerSpecificData: plan ? { plan } : undefined,
};
},
};

View File

@@ -75,3 +75,33 @@ test("Claude OAuth provider always uses the configured redirectUri during token
assert.equal(captured.body.state, "state-from-fragment");
assert.equal(captured.body.code_verifier, "verifier-123");
});
test("Claude OAuth token mapper persists the first non-empty token plan field", () => {
const cases = [
[{ account_tier: " Pro ", plan: "Max" }, "Pro"],
[{ account_tier: "", plan: "Max" }, "Max"],
[{ plan: "", subscription_type: "Team" }, "Team"],
[{ subscription_type: "", billing: { plan: "Enterprise" } }, "Enterprise"],
];
for (const [tokens, expected] of cases) {
const mapped = claude.mapTokens({ access_token: "token-1", ...tokens });
assert.equal(mapped.providerSpecificData.plan, expected);
}
});
test("Claude OAuth token mapper reads plan fields from userinfo extras after token fields", () => {
const mapped = claude.mapTokens(
{ access_token: "token-1" },
{ userInfo: { account_tier: "", subscription_type: "Max" } }
);
assert.equal(mapped.providerSpecificData.plan, "Max");
});
test("Claude OAuth token mapper leaves providerSpecificData.plan undefined without plan fields", () => {
const mapped = claude.mapTokens({ access_token: "token-1", scope: "user:profile" });
assert.equal(mapped.providerSpecificData, undefined);
});

View File

@@ -30,6 +30,17 @@ test("Codex workspacePlanType is used when live plan is missing or unknown", ()
assert.equal(tier.variant, "success");
});
test("Claude providerSpecificData plan is used when live plan is missing", () => {
const resolvedPlan = providerLimitUtils.resolvePlanValue(null, {
plan: "Pro",
});
assert.equal(resolvedPlan, "Pro");
const tier = providerLimitUtils.normalizePlanTier(resolvedPlan);
assert.equal(tier.key, "pro");
assert.equal(tier.variant, "success");
});
test("remaining percentage helpers reflect remaining quota and stale resets refill to 100", () => {
assert.equal(providerLimitUtils.calculatePercentage(0, 100), 100);
assert.equal(providerLimitUtils.calculatePercentage(17, 100), 83);