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 16:02:25 -07:00
parent 93d730c01d
commit 6c3f35dfef
10 changed files with 140 additions and 4 deletions
+2
View File
@@ -5,6 +5,8 @@
### Fixed
- Fixed all extension loading silently failing on the cross-compiled `omp-darwin-arm64` release binary (downloaded directly or via a Homebrew tap wrapper) because `__computeBunfsPackageRoot` mis-handled `import.meta.dir = "//root/omp-darwin-arm64"`. Bun 1.3.14 reports `<bunfs-root>/<binary-name>` for the compiled entry's `import.meta.dir`, but the pre-fix function joined `metaDir + "packages"` and produced `/root/omp-darwin-arm64/packages` — the binary basename was baked into every bunfs path, so the TypeBox/legacy-pi shims and every `@oh-my-pi/pi-*` package-root override failed `existsSync` validation and `resolveCanonicalPiSpecifier` fell through to a bunfs `Bun.resolveSync` that also could not find the module. The function now detects the bunfs-root + binary-basename shape (`path.basename(path.dirname(metaDir)) === "root"`) and strips the trailing binary segment by slicing the original `metaDir`; the production bunfs shim join path also preserves Bun's bunfs-native `//root` / `B:\~BUN\root` prefix that `path.join` would otherwise collapse. ([#3329](https://github.com/can1357/oh-my-pi/issues/3329))
- Fixed `omp usage` and the `/usage` command duplicating provider-wide disclaimer notes (e.g. OpenCode Go's "OMP-observed spend only") once per account × limit window. Provider-level notes now render once above the per-account sections in the TUI, CLI, and ACP render paths, and identical per-limit notes are deduplicated in the TUI aggregate renderer. ([#3268](https://github.com/can1357/oh-my-pi/issues/3268))
## [16.1.16] - 2026-06-23
### Breaking Changes
@@ -450,6 +450,9 @@ export function formatUsageBreakdown(
lines.push(
`${chalk.bold.cyan(formatProviderName(provider))} ${chalk.dim(`— ${accountCount} ${accountCount === 1 ? "account" : "accounts"}`)}`,
);
// Provider-wide disclaimers render once per provider, not per limit.
const providerNotes = [...new Set(providerReports.flatMap(report => report.notes ?? []))];
for (const note of providerNotes) lines.push(` ${chalk.dim(note)}`);
const labelWidth = providerReports
.flatMap(report => report.limits)
@@ -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());
}
@@ -52,6 +52,9 @@ function renderUsageReports(
)) {
lines.push("", formatProviderName(provider));
const activeAccount = resolveActiveAccount?.(provider);
// Provider-wide disclaimers render once per provider, not per limit.
const providerNotes = [...new Set(providerReports.flatMap(report => report.notes ?? []))];
for (const note of providerNotes) lines.push(` ${note}`);
for (const report of providerReports) {
const inUse = reportMatchesActiveAccount(report, activeAccount);
const savedResets = report.resetCredits?.availableCount ?? 0;
+49 -2
View File
@@ -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", () => {