(id, { allowLsStart = true } = {})
| 1521 | * Returns the parsed UserStatus object on success, null on failure. |
| 1522 | */ |
| 1523 | export async function fetchUserStatus(id, { allowLsStart = true } = {}) { |
| 1524 | const account = accounts.find(a => a.id === id); |
| 1525 | if (!account) return null; |
| 1526 | |
| 1527 | const { WindsurfClient } = await import('./client.js'); |
| 1528 | const { ensureLs, getLsFor } = await import('./langserver.js'); |
| 1529 | const proxy = getEffectiveProxy(account.id) || null; |
| 1530 | if (!allowLsStart) { |
| 1531 | const admission = getLsAdmissionForAccount(account.id); |
| 1532 | const skipped = residentProbeSkip(account, admission); |
| 1533 | if (skipped) { |
| 1534 | log.debug(`GetUserStatus ${account.id} skipped: ${skipped.reason} (wouldStart=${!!admission?.wouldStart}, ls=${admission?.key || '?'})`); |
| 1535 | return null; |
| 1536 | } |
| 1537 | } |
| 1538 | if (allowLsStart) await ensureLs(proxy); |
| 1539 | const ls = getLsFor(proxy); |
| 1540 | if (!ls) { log.warn(`No LS for GetUserStatus on ${account.id}`); return null; } |
| 1541 | |
| 1542 | const client = new WindsurfClient(account.apiKey, ls.port, ls.csrfToken); |
| 1543 | let status; |
| 1544 | try { |
| 1545 | status = await client.getUserStatus(); |
| 1546 | } catch (err) { |
| 1547 | log.warn(`GetUserStatus ${safeAccountRef(account)} failed: ${err.message}`); |
| 1548 | return null; |
| 1549 | } |
| 1550 | |
| 1551 | // Apply to account — authoritative tier + entitlement snapshot. |
| 1552 | const prevTier = account.tier; |
| 1553 | account.tier = status.tierName; |
| 1554 | account.userStatus = { |
| 1555 | teamsTier: status.teamsTier, |
| 1556 | pro: status.pro, |
| 1557 | planName: status.planName, |
| 1558 | email: status.email, |
| 1559 | displayName: status.displayName, |
| 1560 | teamId: status.teamId, |
| 1561 | isTeams: status.isTeams, |
| 1562 | isEnterprise: status.isEnterprise, |
| 1563 | hasPaidFeatures: status.hasPaidFeatures, |
| 1564 | trialEndMs: status.trialEndMs, |
| 1565 | promptCreditsUsed: status.userUsedPromptCredits, |
| 1566 | flowCreditsUsed: status.userUsedFlowCredits, |
| 1567 | monthlyPromptCredits: status.monthlyPromptCredits, |
| 1568 | monthlyFlowCredits: status.monthlyFlowCredits, |
| 1569 | maxPremiumChatMessages: status.maxPremiumChatMessages, |
| 1570 | allowedModels: status.allowedModels, |
| 1571 | }; |
| 1572 | account.userStatusLastFetched = Date.now(); |
| 1573 | if (status.email && !account.email.includes('@')) account.email = status.email; |
| 1574 | |
| 1575 | // Mark every cascade-allowed enum as capable; every catalog enum NOT in the |
| 1576 | // allowlist as not-entitled. Pure-UID models (no enum) are left to the |
| 1577 | // canary probe since the server returns allowlists by enum only. |
| 1578 | if (status.allowedModels.length > 0) { |
| 1579 | if (!account.capabilities) account.capabilities = {}; |
| 1580 | const allowedEnums = new Set(status.allowedModels.map(m => m.modelEnum).filter(e => e > 0)); |
no test coverage detected