fix: improve subscription tier detection for Pro accounts

The tier detection was incorrectly showing Pro accounts as "free" due to:
1. paidTier field being flaky/missing from some API responses
2. standard-tier not being recognized as a Pro tier
3. No fallback to allowedTiers when currentTier is missing

Changes:
- Add parseTierId() helper to centralize tier ID parsing
- Recognize "standard-tier" as Pro (Gemini Code Assist paid tier)
- Add fallback chain: paidTier > currentTier > allowedTiers
- Return "unknown" instead of incorrectly defaulting to "free"

Fixes #121

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Badri Narayanan S
2026-01-14 21:27:24 +05:30
parent 772dabe7e4
commit dee7512bd8

View File

@@ -117,6 +117,31 @@ export async function getModelQuotas(token) {
return quotas;
}
/**
* Parse tier ID string to determine subscription level
* @param {string} tierId - The tier ID from the API
* @returns {'free' | 'pro' | 'ultra' | 'unknown'} The subscription tier
*/
function parseTierId(tierId) {
if (!tierId) return 'unknown';
const lower = tierId.toLowerCase();
if (lower.includes('ultra')) {
return 'ultra';
}
if (lower === 'standard-tier') {
// standard-tier = "Gemini Code Assist" (paid, project-based)
return 'pro';
}
if (lower.includes('pro') || lower.includes('premium')) {
return 'pro';
}
if (lower === 'free-tier' || lower.includes('free')) {
return 'free';
}
return 'unknown';
}
/**
* Get subscription tier for an account
* Calls loadCodeAssist API to discover project ID and subscription tier
@@ -162,22 +187,46 @@ export async function getSubscriptionTier(token) {
projectId = data.cloudaicompanionProject.id;
}
// Extract subscription tier (priority: paidTier > currentTier)
let tier = 'free';
const tierId = data.paidTier?.id || data.currentTier?.id;
// Extract subscription tier
// Priority: paidTier > currentTier > allowedTiers
// - paidTier.id: "g1-pro-tier", "g1-ultra-tier" (Google One subscription)
// - currentTier.id: "standard-tier" (pro), "free-tier" (free)
// - allowedTiers: fallback when currentTier is missing
// Note: paidTier is sometimes missing from the response even for Pro accounts
let tier = 'unknown';
let tierId = null;
if (tierId) {
const lowerTier = tierId.toLowerCase();
if (lowerTier.includes('ultra')) {
// 1. Check paidTier first (Google One AI subscription - most reliable)
if (data.paidTier?.id) {
tierId = data.paidTier.id;
const lower = tierId.toLowerCase();
if (lower.includes('ultra')) {
tier = 'ultra';
} else if (lowerTier.includes('pro')) {
} else if (lower.includes('pro')) {
tier = 'pro';
} else {
tier = 'free';
}
}
logger.debug(`[CloudCode] Subscription detected: ${tier}, Project: ${projectId}`);
// 2. Fall back to currentTier if paidTier didn't give us a tier
if (tier === 'unknown' && data.currentTier?.id) {
tierId = data.currentTier.id;
tier = parseTierId(tierId);
}
// 3. Fall back to allowedTiers (find the default or first non-free tier)
if (tier === 'unknown' && Array.isArray(data.allowedTiers) && data.allowedTiers.length > 0) {
// First look for the default tier
let defaultTier = data.allowedTiers.find(t => t?.isDefault);
if (!defaultTier) {
defaultTier = data.allowedTiers[0];
}
if (defaultTier?.id) {
tierId = defaultTier.id;
tier = parseTierId(tierId);
}
}
logger.debug(`[CloudCode] Subscription detected: ${tier} (tierId: ${tierId}), Project: ${projectId}`);
return { tier, projectId };
} catch (error) {