mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 23:02:10 +03:00
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:
@@ -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.",
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user