import type { AuthStorage, UsageReport } from "@oh-my-pi/pi-ai"; /** Identity slice of a stored credential, for "every account" coverage. */ export interface UsageAccountIdentity { provider: string; type: "api_key" | "oauth"; email?: string; accountId?: string; projectId?: string; enterpriseUrl?: string; /** Organization/workspace the credential is scoped to (Anthropic multi-subscription). */ orgId?: string; orgName?: string; /** Epoch ms of the interactive login that minted the OAuth grant (see `OAuthCredentials.authorizedAt`). */ authorizedAt?: number; } /** Flatten every stored credential (OAuth and API key) into identity rows for unreported-account detection. */ export function collectStoredAccounts(authStorage: AuthStorage): UsageAccountIdentity[] { const accounts: UsageAccountIdentity[] = []; const all = authStorage.credentials.all(); for (const provider in all) { const entry = all[provider]; const credentials = Array.isArray(entry) ? entry : [entry]; for (const credential of credentials) { if (credential.type === "oauth") { accounts.push({ provider, type: "oauth", email: credential.email, accountId: credential.accountId, projectId: credential.projectId, enterpriseUrl: credential.enterpriseUrl, orgId: credential.orgId, orgName: credential.orgName, authorizedAt: credential.authorizedAt, }); } else { accounts.push({ provider, type: "api_key" }); } } } return accounts; } /** * Keep only accounts worth a usage row: those whose provider has a usage * provider, so a missing report is a real gap rather than the absence of any * usage concept. Providers with no usage endpoint (web-search keys, local / * keyless servers, inference providers without a usage API) would only ever * render as noise, so they are dropped. * * `hasUsageProvider` is injected (in practice {@link AuthStorage.usage.providerFor}) * so custom/broker resolvers stay authoritative — no provider list is duplicated * here. An explicit `--provider` request bypasses the cull, so * `omp usage --provider xai` can still confirm the stored credential has no * usage endpoint. */ export function selectReportableAccounts( accounts: UsageAccountIdentity[], hasUsageProvider: (provider: string) => boolean, explicitProvider?: string, ): UsageAccountIdentity[] { if (explicitProvider) return accounts; return accounts.filter(account => hasUsageProvider(account.provider)); } /** Lowercased identity strings a report can be attributed to. */ function reportIdentifiers(report: UsageReport): Set { const ids = new Set(); const add = (value: unknown): void => { if (typeof value === "string" && value) ids.add(value.toLowerCase()); }; const meta = report.metadata ?? {}; add(meta.email); add(meta.accountId); add(meta.projectId); add(meta.orgId); for (const limit of report.limits) { add(limit.scope.accountId); add(limit.scope.projectId); add(limit.scope.orgId); } return ids; } /** * Stored credentials that no usage report could be attributed to. * * Conservative on purpose: when a provider's reports or the credential carry * no identity at all (or the credential is an API key alongside existing * reports), we can't attribute, so we don't claim the account is missing. * The tradeoff: once a provider returns any report, its identity-less rows * are never flagged, so a failed fetch for one of several identity-less * logins of the same provider (e.g. two Devin browser logins) goes unshown. */ export function collectUnreportedAccounts( reports: UsageReport[], accounts: UsageAccountIdentity[], ): UsageAccountIdentity[] { const byProvider = new Map(); for (const report of reports) { const list = byProvider.get(report.provider) ?? []; list.push(report); byProvider.set(report.provider, list); } return accounts.filter(account => { const providerReports = byProvider.get(account.provider) ?? []; if (providerReports.length === 0) return true; if (account.type === "api_key") return false; // Org-decisive attribution when EITHER side carries an org (Anthropic // multi-subscription): two orgs share every other identifier, so an // org-scoped account is covered only by its own org's report, and an // identified org-less legacy account is never covered by an org-attributed // sibling report — its own fetch failing must surface as "no usage data". Its // own ORG-LESS report still covers it, though: a mixed pool (fresh // org-scoped logins beside pre-org-capture rows) must not duplicate // every legacy account. The shared org is a GATE, not a match: two Team // members share the org id while drawing on per-user pools, so coverage // also requires the account's own base identity inside the same-org // subset (an org-only account, with no base identifiers, is covered by // any same-org report). The email/account fallback below applies only // when both sides are org-less. When both the account and a report carry // an email, it decides: Codex Team seats share an account id and // Antigravity accounts share one Google project. const accountOrg = account.orgId?.toLowerCase(); const email = account.email?.toLowerCase(); const ids = [account.email, account.accountId, account.projectId] .filter((value): value is string => typeof value === "string" && value.length > 0) .map(value => value.toLowerCase()); if (!accountOrg && ids.length === 0) return false; const covers = (report: UsageReport): boolean => { const reportEmail = report.metadata?.email; if (email && typeof reportEmail === "string" && reportEmail) return reportEmail.toLowerCase() === email; const identifiers = reportIdentifiers(report); return ids.some(id => identifiers.has(id)); }; const sameOrgReports: UsageReport[] = []; let sawReportOrg = false; for (const report of providerReports) { const metaOrg = report.metadata?.orgId; if (typeof metaOrg === "string" && metaOrg) { sawReportOrg = true; if (accountOrg !== undefined && metaOrg.toLowerCase() === accountOrg) sameOrgReports.push(report); } } if (accountOrg || sawReportOrg) { const candidates = accountOrg ? sameOrgReports : providerReports.filter(report => { const metaOrg = report.metadata?.orgId; return !(typeof metaOrg === "string" && metaOrg); }); if (candidates.length === 0) return true; if (ids.length === 0) return false; return !candidates.some(covers); } if (!providerReports.some(report => reportIdentifiers(report).size > 0)) return false; return !providerReports.some(covers); }); } /** Compose the account label from parts, masking each part individually so `--redact` cannot be bypassed by the composite string. */ export function accountIdentityLabel(account: UsageAccountIdentity, redaction?: Map): string { if (account.type === "api_key") return "API key"; const base = account.email ?? account.accountId ?? account.projectId ?? account.enterpriseUrl ?? "OAuth account"; const masked = redaction?.get(base) ?? base; // orgId fallback: the uuid is the actual scoped identity; a token response // can carry it without a display name, and two same-email rows must still // be tellable apart. const org = account.orgName ?? account.orgId; if (!org || org === base) return masked; return `${masked} · ${redaction?.get(org) ?? org}`; }