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:
oldschoola
2026-06-23 06:07:24 -07:00
parent 93d730c01d
commit 6c3f35dfef
10 changed files with 140 additions and 4 deletions
@@ -1583,6 +1583,13 @@ function renderUsageReports(
lines.push(` ${uiTheme.fg("accent", "in use by this session:")} ${activeAccountLabel}`);
}
// Provider-wide disclaimers (e.g. "OMP-observed spend only") render once
// above the per-account sections instead of duplicating onto every limit.
const providerNotes = [...new Set(providerReports.flatMap(report => report.notes ?? []))];
if (providerNotes.length > 0) {
lines.push(` ${uiTheme.fg("dim", providerNotes.join(" • "))}`.trimEnd());
}
const resetAccountLines: string[] = [];
for (const report of providerReports) {
const count = report.resetCredits?.availableCount ?? 0;
@@ -1651,7 +1658,7 @@ function renderUsageReports(
if (resetText) {
lines.push(` ${uiTheme.fg("dim", resetText)}`.trimEnd());
}
const notes = sortedLimits.flatMap(limit => limit.notes ?? []);
const notes = [...new Set(sortedLimits.flatMap(limit => limit.notes ?? []))];
if (notes.length > 0) {
lines.push(` ${uiTheme.fg("dim", notes.join(" • "))}`.trimEnd());
}