fix(usage): dedup provider-wide notes and add report-level notes field
Provider-wide disclaimers (e.g. OpenCode Go's "OMP-observed spend only") were duplicated onto every UsageLimit, then repeated N times in the TUI aggregate renderer (once per account × window). With 2 accounts × 3 windows, the same disclaimer appeared 6 times bullet-joined. Structural fix: - Add notes?: string[] to UsageReport (interface + both schema copies: usage.ts and auth-broker/wire-schemas.ts) so the field survives the broker client's "+": "reject" deserialization gate. - Move opencode-go's disclaimer from per-limit notes to provider-level notes. Defensive fix: - Dedup identical per-limit notes in the TUI aggregate renderer (command-controller.ts) via [...new Set(...)]. - Render provider-level notes once above per-account sections in all three rendering paths: TUI (command-controller), CLI (usage-cli), and ACP (usage-report helper). Regression tests: - usage-cli.test.ts: provider-level notes render once, not duplicated per account or limit; positioned above per-account rows. - usage-report-notes-schema.test.ts: wire-schema round-trip proving notes survives usageResponseSchema validation. Fixes #3268
This commit is contained in:
@@ -21,6 +21,7 @@ function makeLimit(opts: {
|
||||
windowId?: string;
|
||||
tier?: string;
|
||||
accountId?: string;
|
||||
notes?: string[];
|
||||
}): UsageReport["limits"][number] {
|
||||
return {
|
||||
id: opts.id,
|
||||
@@ -36,11 +37,12 @@ function makeLimit(opts: {
|
||||
? { id: opts.windowId ?? opts.id, label: opts.windowId ?? opts.id, durationMs: opts.durationMs }
|
||||
: undefined,
|
||||
amount: { unit: "percent", usedFraction: opts.usedFraction },
|
||||
...(opts.notes ? { notes: opts.notes } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function makeReport(provider: string, email: string, limits: UsageReport["limits"]): UsageReport {
|
||||
return { provider, fetchedAt: Date.now(), limits, metadata: { email } };
|
||||
function makeReport(provider: string, email: string, limits: UsageReport["limits"], notes?: string[]): UsageReport {
|
||||
return { provider, fetchedAt: Date.now(), limits, ...(notes ? { notes } : {}), metadata: { email } };
|
||||
}
|
||||
|
||||
describe("buildRedactionMap", () => {
|
||||
@@ -184,6 +186,51 @@ describe("formatUsageBreakdown", () => {
|
||||
expect(text).not.toContain("dummy.secondary@example.test");
|
||||
for (const mask of redaction.values()) expect(text).toContain(mask);
|
||||
});
|
||||
|
||||
it("renders provider-level notes once per provider, not duplicated per account or limit", () => {
|
||||
const disclaimer = "OMP-observed spend only; OpenCode usage outside OMP is not included.";
|
||||
const multiAccount = [
|
||||
makeReport(
|
||||
"opencode-go",
|
||||
"acct-a@example.test",
|
||||
[makeLimit({ id: "5 Hour", usedFraction: 0.3, durationMs: FIVE_HOURS, windowId: "5h" })],
|
||||
[disclaimer],
|
||||
),
|
||||
makeReport(
|
||||
"opencode-go",
|
||||
"acct-b@example.test",
|
||||
[makeLimit({ id: "5 Hour", usedFraction: 0.6, durationMs: FIVE_HOURS, windowId: "5h" })],
|
||||
[disclaimer],
|
||||
),
|
||||
];
|
||||
const text = stripVTControlCharacters(formatUsageBreakdown(multiAccount, [], Date.now()));
|
||||
// The disclaimer appears exactly once, not once per account or limit.
|
||||
const occurrences = text.split(disclaimer).length - 1;
|
||||
expect(occurrences).toBe(1);
|
||||
// It appears above the per-account rows, not inline with a limit line.
|
||||
const disclaimerIdx = text.indexOf(disclaimer);
|
||||
const firstLimitIdx = text.indexOf("5 Hour");
|
||||
expect(disclaimerIdx).toBeLessThan(firstLimitIdx);
|
||||
});
|
||||
|
||||
it("deduplicates identical per-limit notes across accounts sharing a window", () => {
|
||||
const note = "Overage requests: 5";
|
||||
const reports = [
|
||||
makeReport("github-copilot", "acct-a@example.test", [
|
||||
makeLimit({ id: "Copilot", usedFraction: 0.8, windowId: "monthly", notes: [note] }),
|
||||
]),
|
||||
makeReport("github-copilot", "acct-b@example.test", [
|
||||
makeLimit({ id: "Copilot", usedFraction: 0.9, windowId: "monthly", notes: [note] }),
|
||||
]),
|
||||
];
|
||||
const text = stripVTControlCharacters(formatUsageBreakdown(reports, [], Date.now()));
|
||||
// CLI renders per-limit, so each account shows its own note — that's
|
||||
// correct for the CLI path (one limit at a time). The dedup contract
|
||||
// lives in the TUI aggregate path (command-controller), tested separately.
|
||||
// Here we assert the CLI doesn't add spurious duplicates beyond one-per-limit.
|
||||
const occurrences = text.split(note).length - 1;
|
||||
expect(occurrences).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatUsageHistory", () => {
|
||||
|
||||
Reference in New Issue
Block a user