mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(agent-usage): centralize DisplayTotal, re-sort by display tier, split caveat
Fold Thufir's three blocking amendments into cbfbe1ed0:
1. Centralize display metric as DisplayTotal {kind, value, partial}.
deriveApproxTotal() replaced by deriveDisplayTotal() returning an
explicit exact|approximate|unknown discriminant with provenance:
- exact: totalTokens.value present; partial mirrors wire incomplete flag
- approximate: totalTokens null, i/o known; value = bigint-safe i/o sum;
partial = inputTokens.incomplete || outputTokens.incomplete
- unknown: no token counts at all; value null, partial false
All surfaces (Section header/bars/rows, DailyBars, FocusedView stat)
now consume this single result — no surface can drift independently.
2. Re-sort agents/models by display tier.
sortAgentsByKnownTotal/sortModelsByKnownTotal replaced by
sortAgentsByDisplayTotal/sortModelsByDisplayTotal using a three-tier
key (exact=0, approximate=1, unknown=2), descending by value within
tier, then existing tiebreaks. With all-null prod totals the list
order and bar widths now agree instead of disagreeing.
3. Split caveat paragraph into per-condition sentences.
The single agent-usage-focused-partial-explanation block replaced by
two separately-gated <p> elements:
- agent-usage-focused-unknown-intervals-caveat: gates on hasUnknownUsage
- agent-usage-focused-invalid-reports-caveat: gates on invalidReportCount > 0
Each sentence claims only what its gate proves.
Test updates:
- deriveApproxTotal tests replaced with deriveDisplayTotal tests covering
all three kinds, partial provenance, and i/o-only combinations.
- Sort tests updated for new export names and assertions.
- Mixed exact/approximate/unknown population tests added for both
sortAgentsByDisplayTotal and sortModelsByDisplayTotal.
- e2e spec updated from single partial-explanation testid to the two
per-condition testids.
- 3567 tests pass, tsc --noEmit clean.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
co-authored by
Will Pfleger
parent
cbfbe1ed05
commit
b384fc9a56
@@ -4,7 +4,7 @@ import test from "node:test";
|
||||
import {
|
||||
bigintRatio,
|
||||
buildLocalDayBoundaries,
|
||||
deriveApproxTotal,
|
||||
deriveDisplayTotal,
|
||||
deriveUsageIngressTrailing,
|
||||
formatCoverageDate,
|
||||
formatEstimatedCostUsd,
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
isUnknownField,
|
||||
msUntilNextLocalMidnight,
|
||||
parseTokenCount,
|
||||
sortAgentsByKnownTotal,
|
||||
sortModelsByKnownTotal,
|
||||
sortAgentsByDisplayTotal,
|
||||
sortModelsByDisplayTotal,
|
||||
sumKnownBucketTotals,
|
||||
} from "./agentUsage.ts";
|
||||
|
||||
@@ -334,122 +334,214 @@ test("bigintRatio clamps part to [0, whole]", () => {
|
||||
assert.equal(bigintRatio(200n, 100n), 1);
|
||||
});
|
||||
|
||||
// ── deriveApproxTotal ─────────────────────────────────────────────────────────
|
||||
// ── deriveDisplayTotal ────────────────────────────────────────────────────────
|
||||
|
||||
test("deriveApproxTotal returns null when genuine total is known (no approximation needed)", () => {
|
||||
test("deriveDisplayTotal returns exact kind when totalTokens is present", () => {
|
||||
const usage = reportedUsage({
|
||||
inputTokens: usageField({ value: "800" }),
|
||||
outputTokens: usageField({ value: "200" }),
|
||||
totalTokens: usageField({ value: "1100" }),
|
||||
});
|
||||
assert.equal(deriveApproxTotal(usage), null);
|
||||
const dt = deriveDisplayTotal(usage);
|
||||
assert.equal(dt.kind, "exact");
|
||||
assert.equal(dt.value, 1100n);
|
||||
assert.equal(dt.partial, false);
|
||||
});
|
||||
|
||||
test("deriveApproxTotal sums input and output when total is null", () => {
|
||||
test("deriveDisplayTotal carries partial=true for an exact total flagged incomplete", () => {
|
||||
const usage = reportedUsage({
|
||||
totalTokens: usageField({ value: "900", incomplete: true }),
|
||||
});
|
||||
const dt = deriveDisplayTotal(usage);
|
||||
assert.equal(dt.kind, "exact");
|
||||
assert.equal(dt.value, 900n);
|
||||
assert.equal(dt.partial, true);
|
||||
});
|
||||
|
||||
test("deriveDisplayTotal returns approximate kind when totalTokens is null but i/o is known", () => {
|
||||
const usage = reportedUsage({
|
||||
inputTokens: usageField({ value: "800" }),
|
||||
outputTokens: usageField({ value: "200" }),
|
||||
});
|
||||
assert.equal(deriveApproxTotal(usage), 1000n);
|
||||
const dt = deriveDisplayTotal(usage);
|
||||
assert.equal(dt.kind, "approximate");
|
||||
assert.equal(dt.value, 1000n);
|
||||
assert.equal(dt.partial, false);
|
||||
});
|
||||
|
||||
test("deriveApproxTotal returns input alone when output is null", () => {
|
||||
test("deriveDisplayTotal approximate partial=true when either i/o field is incomplete", () => {
|
||||
const usage = reportedUsage({
|
||||
inputTokens: usageField({ value: "800", incomplete: true }),
|
||||
outputTokens: usageField({ value: "200" }),
|
||||
});
|
||||
const dt = deriveDisplayTotal(usage);
|
||||
assert.equal(dt.kind, "approximate");
|
||||
assert.equal(dt.partial, true);
|
||||
});
|
||||
|
||||
test("deriveDisplayTotal returns approximate from input alone when output is null", () => {
|
||||
const usage = reportedUsage({
|
||||
inputTokens: usageField({ value: "500" }),
|
||||
});
|
||||
assert.equal(deriveApproxTotal(usage), 500n);
|
||||
const dt = deriveDisplayTotal(usage);
|
||||
assert.equal(dt.kind, "approximate");
|
||||
assert.equal(dt.value, 500n);
|
||||
});
|
||||
|
||||
test("deriveApproxTotal returns output alone when input is null", () => {
|
||||
test("deriveDisplayTotal returns approximate from output alone when input is null", () => {
|
||||
const usage = reportedUsage({
|
||||
outputTokens: usageField({ value: "300" }),
|
||||
});
|
||||
assert.equal(deriveApproxTotal(usage), 300n);
|
||||
const dt = deriveDisplayTotal(usage);
|
||||
assert.equal(dt.kind, "approximate");
|
||||
assert.equal(dt.value, 300n);
|
||||
});
|
||||
|
||||
test("deriveApproxTotal returns null when total, input, and output are all null", () => {
|
||||
test("deriveDisplayTotal returns unknown kind when all fields are null", () => {
|
||||
const usage = reportedUsage();
|
||||
assert.equal(deriveApproxTotal(usage), null);
|
||||
const dt = deriveDisplayTotal(usage);
|
||||
assert.equal(dt.kind, "unknown");
|
||||
assert.equal(dt.value, null);
|
||||
assert.equal(dt.partial, false);
|
||||
});
|
||||
|
||||
// ── sortAgentsByKnownTotal / sortModelsByKnownTotal ─────────────────────────
|
||||
// ── sortAgentsByDisplayTotal / sortModelsByDisplayTotal ─────────────────────
|
||||
|
||||
test("sortAgentsByKnownTotal ranks known totals descending", () => {
|
||||
test("sortAgentsByDisplayTotal ranks known exact totals descending", () => {
|
||||
const agents = [
|
||||
agentUsage("a1", "100"),
|
||||
agentUsage("a2", "300"),
|
||||
agentUsage("a3", "200"),
|
||||
];
|
||||
const sorted = sortAgentsByKnownTotal(agents);
|
||||
const sorted = sortAgentsByDisplayTotal(agents);
|
||||
assert.deepEqual(
|
||||
sorted.map((a) => a.agentPubkey),
|
||||
["a2", "a3", "a1"],
|
||||
);
|
||||
});
|
||||
|
||||
test("sortAgentsByKnownTotal lists unknown-total agents after all known-total agents, never interleaved", () => {
|
||||
test("sortAgentsByDisplayTotal ranks exact totals above approximate totals", () => {
|
||||
const exactAgent = agentUsage("exact", "50");
|
||||
const approxAgent = agentUsage("approx", null, {
|
||||
usage: reportedUsage({
|
||||
inputTokens: usageField({ value: "9000" }),
|
||||
outputTokens: usageField({ value: "9000" }),
|
||||
}),
|
||||
});
|
||||
const sorted = sortAgentsByDisplayTotal([approxAgent, exactAgent]);
|
||||
// exact(50) < approximate(18000) numerically, but exact tier wins
|
||||
assert.equal(sorted[0].agentPubkey, "exact");
|
||||
assert.equal(sorted[1].agentPubkey, "approx");
|
||||
});
|
||||
|
||||
test("sortAgentsByDisplayTotal ranks approximate totals above unknown totals", () => {
|
||||
const approxAgent = agentUsage("approx", null, {
|
||||
usage: reportedUsage({
|
||||
inputTokens: usageField({ value: "100" }),
|
||||
}),
|
||||
});
|
||||
const unknownAgent = agentUsage("unknown", null);
|
||||
const sorted = sortAgentsByDisplayTotal([unknownAgent, approxAgent]);
|
||||
assert.equal(sorted[0].agentPubkey, "approx");
|
||||
assert.equal(sorted[1].agentPubkey, "unknown");
|
||||
});
|
||||
|
||||
test("sortAgentsByDisplayTotal handles mixed exact/approximate/unknown population in tier order", () => {
|
||||
const agents = [
|
||||
agentUsage("u1", null), // unknown
|
||||
agentUsage("e1", "100"), // exact
|
||||
agentUsage("a1", null, { // approximate
|
||||
usage: reportedUsage({ inputTokens: usageField({ value: "500" }) }),
|
||||
}),
|
||||
agentUsage("u2", null), // unknown
|
||||
agentUsage("e2", "300"), // exact
|
||||
agentUsage("a2", null, { // approximate
|
||||
usage: reportedUsage({ inputTokens: usageField({ value: "200" }) }),
|
||||
}),
|
||||
];
|
||||
const sorted = sortAgentsByDisplayTotal(agents);
|
||||
// Tier order: exact first (e2=300 > e1=100), then approx (a1=500 > a2=200), then unknown (u1 < u2 by pubkey)
|
||||
assert.deepEqual(
|
||||
sorted.map((a) => a.agentPubkey),
|
||||
["e2", "e1", "a1", "a2", "u1", "u2"],
|
||||
);
|
||||
});
|
||||
|
||||
test("sortAgentsByDisplayTotal lists unknown-total agents after all other agents, tiebroken by pubkey", () => {
|
||||
const agents = [
|
||||
agentUsage("unknown-b", null),
|
||||
agentUsage("known", "50"),
|
||||
agentUsage("unknown-a", null),
|
||||
];
|
||||
const sorted = sortAgentsByKnownTotal(agents);
|
||||
const sorted = sortAgentsByDisplayTotal(agents);
|
||||
assert.equal(sorted[0].agentPubkey, "known");
|
||||
// Unknown-total agents tiebreak by normalized pubkey.
|
||||
assert.deepEqual(
|
||||
sorted.slice(1).map((a) => a.agentPubkey),
|
||||
["unknown-a", "unknown-b"],
|
||||
);
|
||||
});
|
||||
|
||||
test("sortAgentsByKnownTotal tiebreaks equal known totals by pubkey", () => {
|
||||
test("sortAgentsByDisplayTotal tiebreaks equal exact totals by pubkey", () => {
|
||||
const agents = [agentUsage("b", "100"), agentUsage("a", "100")];
|
||||
const sorted = sortAgentsByKnownTotal(agents);
|
||||
const sorted = sortAgentsByDisplayTotal(agents);
|
||||
assert.deepEqual(
|
||||
sorted.map((a) => a.agentPubkey),
|
||||
["a", "b"],
|
||||
);
|
||||
});
|
||||
|
||||
test("sortModelsByKnownTotal sorts null model ('Unknown model') last among ties", () => {
|
||||
test("sortModelsByDisplayTotal sorts null model ('Unknown model') last among ties", () => {
|
||||
const models = [
|
||||
modelUsage(null, "100"),
|
||||
modelUsage("gpt-4", "100"),
|
||||
modelUsage("claude", "100"),
|
||||
];
|
||||
const sorted = sortModelsByKnownTotal(models);
|
||||
const sorted = sortModelsByDisplayTotal(models);
|
||||
assert.deepEqual(
|
||||
sorted.map((m) => m.model),
|
||||
["claude", "gpt-4", null],
|
||||
);
|
||||
});
|
||||
|
||||
test("sortModelsByKnownTotal tiebreaks harness before model when totals are equal", () => {
|
||||
test("sortModelsByDisplayTotal tiebreaks harness before model when totals are equal", () => {
|
||||
const models = [
|
||||
modelUsage("m", "100", { harness: "z-harness" }),
|
||||
modelUsage("m", "100", { harness: "a-harness" }),
|
||||
modelUsage("m", "100", { harness: null }),
|
||||
];
|
||||
const sorted = sortModelsByKnownTotal(models);
|
||||
const sorted = sortModelsByDisplayTotal(models);
|
||||
assert.deepEqual(
|
||||
sorted.map((m) => m.harness),
|
||||
["a-harness", "z-harness", null],
|
||||
);
|
||||
});
|
||||
|
||||
test("sortModelsByKnownTotal same model two harnesses produces two rows in harness order", () => {
|
||||
test("sortModelsByDisplayTotal same model two harnesses produces two rows in harness order", () => {
|
||||
// Same model via two harnesses should be distinct rows; harness-ascending tiebreak.
|
||||
const models = [
|
||||
modelUsage("claude-sonnet", "500", { harness: "goose" }),
|
||||
modelUsage("claude-sonnet", "500", { harness: "claude-code" }),
|
||||
];
|
||||
const sorted = sortModelsByKnownTotal(models);
|
||||
const sorted = sortModelsByDisplayTotal(models);
|
||||
assert.deepEqual(
|
||||
sorted.map((m) => m.harness),
|
||||
["claude-code", "goose"],
|
||||
);
|
||||
});
|
||||
|
||||
test("sortModelsByDisplayTotal ranks exact tier above approximate tier regardless of value", () => {
|
||||
const exactModel = modelUsage("small-model", "10");
|
||||
const approxModel = modelUsage("big-approx", null, {
|
||||
usage: reportedUsage({
|
||||
inputTokens: usageField({ value: "9999" }),
|
||||
outputTokens: usageField({ value: "9999" }),
|
||||
}),
|
||||
});
|
||||
const sorted = sortModelsByDisplayTotal([approxModel, exactModel]);
|
||||
assert.equal(sorted[0].model, "small-model"); // exact tier wins
|
||||
assert.equal(sorted[1].model, "big-approx");
|
||||
});
|
||||
|
||||
// ── isPartialField / isUnknownField ──────────────────────────────────────────
|
||||
|
||||
test("isPartialField is true only for a known value flagged incomplete", () => {
|
||||
|
||||
@@ -189,85 +189,119 @@ export function bigintRatio(part: bigint, whole: bigint): number {
|
||||
return Number(permille) / 1000;
|
||||
}
|
||||
|
||||
// ── Display approximation (A2 presentation layer) ────────────────────────────
|
||||
// ── Display total derivation (A2 presentation layer) ─────────────────────────
|
||||
|
||||
/**
|
||||
* Derive a display approximation of the total by summing known input and output
|
||||
* token counts. Returns `null` if neither field is known. This is a *display
|
||||
* label* only — it is NEVER written to the wire or stored; NIP-AM's
|
||||
* "MUST NOT derive total = input + output" governs published/stored data only.
|
||||
* Callers MUST prefix the result with `≈` so the approximation is honest.
|
||||
* A provenance-bearing display total for the usage UI. Only one of three
|
||||
* states is ever active:
|
||||
*
|
||||
* - `exact`: `totalTokens.value` is present and parsed. `partial` mirrors the
|
||||
* wire field's `incomplete` flag.
|
||||
* - `approximate`: `totalTokens.value` is absent but at least one of
|
||||
* `inputTokens` / `outputTokens` is known; `value` is their bigint-safe sum.
|
||||
* `partial` is `inputTokens.incomplete || outputTokens.incomplete`.
|
||||
* Callers MUST render `≈` to distinguish this from a provider total.
|
||||
* - `unknown`: no token counts are available at all; `value` is `null`.
|
||||
*
|
||||
* This is a *display* value only — it is NEVER written to the wire or stored.
|
||||
* NIP-AM's "MUST NOT derive total = input + output" governs published/stored
|
||||
* data; this label lives entirely in the presentation layer.
|
||||
*/
|
||||
export function deriveApproxTotal(usage: {
|
||||
export type DisplayTotal =
|
||||
| { kind: "exact"; value: bigint; partial: boolean }
|
||||
| { kind: "approximate"; value: bigint; partial: boolean }
|
||||
| { kind: "unknown"; value: null; partial: false };
|
||||
|
||||
export function deriveDisplayTotal(usage: {
|
||||
inputTokens: UsageField;
|
||||
outputTokens: UsageField;
|
||||
totalTokens: UsageField;
|
||||
}): bigint | null {
|
||||
if (parseTokenCount(usage.totalTokens.value) !== null) {
|
||||
// Genuine total is known — callers should use it directly; no approximation needed.
|
||||
return null;
|
||||
}): DisplayTotal {
|
||||
const exact = parseTokenCount(usage.totalTokens.value);
|
||||
if (exact !== null) {
|
||||
return { kind: "exact", value: exact, partial: isPartialField(usage.totalTokens) };
|
||||
}
|
||||
const input = parseTokenCount(usage.inputTokens.value);
|
||||
const output = parseTokenCount(usage.outputTokens.value);
|
||||
if (input === null && output === null) return null;
|
||||
return (input ?? 0n) + (output ?? 0n);
|
||||
if (input !== null || output !== null) {
|
||||
return {
|
||||
kind: "approximate",
|
||||
value: (input ?? 0n) + (output ?? 0n),
|
||||
partial: isPartialField(usage.inputTokens) || isPartialField(usage.outputTokens),
|
||||
};
|
||||
}
|
||||
return { kind: "unknown", value: null, partial: false };
|
||||
}
|
||||
|
||||
// ── Ranking (A2: known lower-bound totals rank; null totals list after) ─────
|
||||
// ── Ranking (A2: rank by display total — exact > approximate > unknown) ──────
|
||||
|
||||
type Ranked<T> = { item: T; totalTokens: bigint | null };
|
||||
type DisplayTierKey = 0 | 1 | 2; // 0 = exact, 1 = approximate, 2 = unknown
|
||||
|
||||
function rankByKnownTotal<T>(
|
||||
type RankedWithDisplay<T> = {
|
||||
item: T;
|
||||
displayTotal: DisplayTotal;
|
||||
tierKey: DisplayTierKey;
|
||||
};
|
||||
|
||||
function tierOf(dt: DisplayTotal): DisplayTierKey {
|
||||
if (dt.kind === "exact") return 0;
|
||||
if (dt.kind === "approximate") return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort items by their display total:
|
||||
* 1. Exact totals rank first, descending by value.
|
||||
* 2. Approximate totals (≈ in+out) rank next, descending by value.
|
||||
* 3. Unknown totals rank last, unordered beyond the tiebreak.
|
||||
* Within the same tier and value, `tiebreak` resolves the order.
|
||||
*/
|
||||
function rankByDisplayTotal<T>(
|
||||
items: readonly T[],
|
||||
totalTokens: (item: T) => UsageField,
|
||||
getUsage: (item: T) => { inputTokens: UsageField; outputTokens: UsageField; totalTokens: UsageField },
|
||||
tiebreak: (a: T, b: T) => number,
|
||||
): T[] {
|
||||
const withTotals: Ranked<T>[] = items.map((item) => ({
|
||||
item,
|
||||
totalTokens: parseTokenCount(totalTokens(item).value),
|
||||
}));
|
||||
const withDisplay: RankedWithDisplay<T>[] = items.map((item) => {
|
||||
const dt = deriveDisplayTotal(getUsage(item));
|
||||
return { item, displayTotal: dt, tierKey: tierOf(dt) };
|
||||
});
|
||||
|
||||
return withTotals
|
||||
return withDisplay
|
||||
.sort((a, b) => {
|
||||
if (a.totalTokens !== null && b.totalTokens !== null) {
|
||||
if (a.totalTokens !== b.totalTokens) {
|
||||
return a.totalTokens > b.totalTokens ? -1 : 1;
|
||||
if (a.tierKey !== b.tierKey) return a.tierKey - b.tierKey;
|
||||
// Same tier — for exact/approximate, sort descending by value.
|
||||
if (a.displayTotal.value !== null && b.displayTotal.value !== null) {
|
||||
if (a.displayTotal.value !== b.displayTotal.value) {
|
||||
return a.displayTotal.value > b.displayTotal.value ? -1 : 1;
|
||||
}
|
||||
return tiebreak(a.item, b.item);
|
||||
}
|
||||
// Known-total rows rank before unknown-total rows; never interleave.
|
||||
if (a.totalTokens !== null) return -1;
|
||||
if (b.totalTokens !== null) return 1;
|
||||
return tiebreak(a.item, b.item);
|
||||
})
|
||||
.map((ranked) => ranked.item);
|
||||
}
|
||||
|
||||
/** Agents sort by known `totalTokens` descending, then normalized pubkey (A2/plan). Unknown-total agents list after all known-total agents, unranked among themselves beyond the pubkey tiebreak. */
|
||||
export function sortAgentsByKnownTotal(
|
||||
/** Agents sort by display total (exact → approximate → unknown), descending by value within tier, then normalized pubkey. */
|
||||
export function sortAgentsByDisplayTotal(
|
||||
agents: readonly AgentUsage[],
|
||||
): AgentUsage[] {
|
||||
return rankByKnownTotal(
|
||||
return rankByDisplayTotal(
|
||||
agents,
|
||||
(agent) => agent.usage.totalTokens,
|
||||
(agent) => agent.usage,
|
||||
(a, b) => a.agentPubkey.localeCompare(b.agentPubkey),
|
||||
);
|
||||
}
|
||||
|
||||
/** Model rows use the same ranking rule as agents, tiebroken by harness name
|
||||
/** Model rows use the same display-total ranking, tiebroken by harness name
|
||||
* (null harness sorts last), then by model name (null model sorts last).
|
||||
* Ordinal (`<`/`>`) comparators are used so ordering is locale-independent
|
||||
* and matches the Rust backend's `String::cmp` byte order.
|
||||
* Note: harness/model identifiers are ASCII in practice; UTF-16 vs UTF-8
|
||||
* scalar divergence for astral code points is accepted and not a use case. */
|
||||
export function sortModelsByKnownTotal(
|
||||
* and matches the Rust backend's `String::cmp` byte order. */
|
||||
export function sortModelsByDisplayTotal(
|
||||
models: readonly AgentUsageModel[],
|
||||
): AgentUsageModel[] {
|
||||
return rankByKnownTotal(
|
||||
return rankByDisplayTotal(
|
||||
models,
|
||||
(model) => model.usage.totalTokens,
|
||||
(model) => model.usage,
|
||||
(a, b) => {
|
||||
// Harness tiebreak first (ordinal, None last).
|
||||
const harnessCmp =
|
||||
a.harness === b.harness
|
||||
? 0
|
||||
@@ -279,7 +313,6 @@ export function sortModelsByKnownTotal(
|
||||
? -1
|
||||
: 1;
|
||||
if (harnessCmp !== 0) return harnessCmp;
|
||||
// Then model (ordinal, None last).
|
||||
if (a.model === b.model) return 0;
|
||||
if (a.model === null) return 1;
|
||||
if (b.model === null) return -1;
|
||||
@@ -370,9 +403,9 @@ export function sumKnownBucketTotals(
|
||||
partial = true;
|
||||
}
|
||||
if (known === null) {
|
||||
const approx = deriveApproxTotal(bucket.usage);
|
||||
if (approx !== null) {
|
||||
approxSum += approx;
|
||||
const dt = deriveDisplayTotal(bucket.usage);
|
||||
if (dt.kind === "approximate") {
|
||||
approxSum += dt.value;
|
||||
sawApprox = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { cn } from "@/shared/lib/cn";
|
||||
import type { AgentUsageSeriesBucket } from "@/shared/api/tauriArchive";
|
||||
import {
|
||||
bigintRatio,
|
||||
deriveApproxTotal,
|
||||
deriveDisplayTotal,
|
||||
formatTokenCountCompact,
|
||||
isPartialField,
|
||||
parseTokenCount,
|
||||
@@ -66,13 +66,13 @@ function deriveBarState(bucket: AgentUsageSeriesBucket) {
|
||||
knownTokens: known,
|
||||
};
|
||||
}
|
||||
// Genuine total unknown — try i/o approximation before falling back to hatched.
|
||||
const approx = deriveApproxTotal(bucket.usage);
|
||||
if (approx !== null) {
|
||||
// Genuine total unknown — derive the display total for the bar.
|
||||
const dt = deriveDisplayTotal(bucket.usage);
|
||||
if (dt.kind === "approximate") {
|
||||
return {
|
||||
accessibleLabel: `${dateLabel} · ≈ ${formatTokenCountCompact(approx)} tokens (approx)`,
|
||||
accessibleLabel: `${dateLabel} · ≈ ${formatTokenCountCompact(dt.value)} tokens (approx)`,
|
||||
kind: "approx" as const,
|
||||
knownTokens: approx,
|
||||
knownTokens: dt.value,
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -101,10 +101,10 @@ export function AgentUsageDailyBars({
|
||||
buckets.reduce<bigint>((max, bucket) => {
|
||||
const total = parseTokenCount(bucket.usage.totalTokens.value);
|
||||
if (total !== null) return total > max ? total : max;
|
||||
// Fall back to the i/o approximation so bars scale correctly when
|
||||
// no bucket reports a genuine total.
|
||||
const approx = deriveApproxTotal(bucket.usage);
|
||||
return approx !== null && approx > max ? approx : max;
|
||||
// Fall back to the display total's approximate value so bars scale
|
||||
// correctly when no bucket reports a genuine total.
|
||||
const dt = deriveDisplayTotal(bucket.usage);
|
||||
return dt.kind === "approximate" && dt.value > max ? dt.value : max;
|
||||
}, 0n),
|
||||
[buckets],
|
||||
);
|
||||
|
||||
@@ -14,15 +14,16 @@ import { Skeleton } from "@/shared/ui/skeleton";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/shared/ui/tabs";
|
||||
import { useAgentUsageSeries } from "../hooks";
|
||||
import {
|
||||
deriveDisplayTotal,
|
||||
formatCoverageDate,
|
||||
formatEstimatedCostUsd,
|
||||
formatTokenCountCompact,
|
||||
formatTokenCountExact,
|
||||
deriveApproxTotal,
|
||||
isPartialField,
|
||||
isUnknownField,
|
||||
parseTokenCount,
|
||||
sortModelsByKnownTotal,
|
||||
sortModelsByDisplayTotal,
|
||||
type DisplayTotal,
|
||||
type UsageWindowDays,
|
||||
} from "../lib/agentUsage";
|
||||
import { AgentUsageDailyBars } from "./AgentUsageDailyBars";
|
||||
@@ -204,28 +205,29 @@ function AgentUsageFocusedTotals({
|
||||
agent: AgentUsageSeries["agents"][number];
|
||||
coverage: AgentUsageSeries["coverage"];
|
||||
}) {
|
||||
const { estimatedCostUsd, inputTokens, outputTokens, totalTokens } =
|
||||
const { estimatedCostUsd, inputTokens, outputTokens } =
|
||||
agent.usage;
|
||||
const models = sortModelsByKnownTotal(agent.models);
|
||||
// Show the caveat paragraph only when there are genuinely invalid/excluded
|
||||
// rows or truly unknown i/o deltas. `agent.hasUnknownUsage` is true when
|
||||
// deltaReliable is false (some intervals couldn't be computed). The
|
||||
// `coverage.invalidReportCount > 0` path covers rows excluded from buckets.
|
||||
// We do NOT trigger on totalTokens.value being null: that's the permanent
|
||||
// state for all real publishers today (no harness emits a total yet), and
|
||||
// showing the caveat permanently would make it read as a persistent error.
|
||||
const explainPartial =
|
||||
agent.hasUnknownUsage || coverage.invalidReportCount > 0;
|
||||
const models = sortModelsByDisplayTotal(agent.models);
|
||||
// `explainPartial` controls the caveat paragraph. Each sentence is gated
|
||||
// only on the condition that proves it:
|
||||
// - unknown-intervals sentence: `agent.hasUnknownUsage` — true when
|
||||
// deltaReliable is false for at least one interval in the window.
|
||||
// - invalid-reports sentence: `coverage.invalidReportCount > 0` — true
|
||||
// when rows were excluded from buckets due to bad timestamps or missing
|
||||
// session cumulative totals.
|
||||
// We do NOT trigger on totalTokens.value being null — that's the permanent
|
||||
// state for all real publishers today, not a data quality problem.
|
||||
const showUnknownIntervalsCaveat = agent.hasUnknownUsage;
|
||||
const showInvalidReportsCaveat = coverage.invalidReportCount > 0;
|
||||
|
||||
// Approximation for the Total tokens stat when no genuine total is available.
|
||||
const approxTotal = deriveApproxTotal(agent.usage);
|
||||
// Display total for the Total tokens stat.
|
||||
const displayTotal = deriveDisplayTotal(agent.usage);
|
||||
|
||||
return (
|
||||
<Card className="space-y-4 p-6" data-testid="agent-usage-focused-totals">
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
<ApproxTokenStat
|
||||
approxTotal={approxTotal}
|
||||
field={totalTokens}
|
||||
displayTotal={displayTotal}
|
||||
label="Total tokens"
|
||||
/>
|
||||
<TokenStat field={inputTokens} label="Input tokens" />
|
||||
@@ -300,11 +302,19 @@ function AgentUsageFocusedTotals({
|
||||
{" · "}
|
||||
{formatCoverageRange(coverage)}
|
||||
</p>
|
||||
{explainPartial ? (
|
||||
<p data-testid="agent-usage-focused-partial-explanation">
|
||||
Some usage could not be counted: reports with an unreadable
|
||||
timestamp or a cumulative total missing its session are excluded,
|
||||
and unknown intervals are omitted rather than shown as zero.
|
||||
{showUnknownIntervalsCaveat ? (
|
||||
<p data-testid="agent-usage-focused-unknown-intervals-caveat">
|
||||
Some usage could not be counted: unknown intervals are omitted
|
||||
rather than shown as zero.
|
||||
</p>
|
||||
) : null}
|
||||
{showInvalidReportsCaveat ? (
|
||||
<p data-testid="agent-usage-focused-invalid-reports-caveat">
|
||||
{coverage.invalidReportCount === 1
|
||||
? "1 report"
|
||||
: `${coverage.invalidReportCount} reports`}{" "}
|
||||
excluded: reports with an unreadable timestamp or a cumulative
|
||||
total missing its session are not assigned to any day.
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -335,23 +345,20 @@ function TokenStat({
|
||||
* honest without hiding that real token activity was counted.
|
||||
*/
|
||||
function ApproxTokenStat({
|
||||
approxTotal,
|
||||
field,
|
||||
displayTotal,
|
||||
label,
|
||||
}: {
|
||||
approxTotal: bigint | null;
|
||||
field: { value: string | null; incomplete: boolean };
|
||||
displayTotal: DisplayTotal;
|
||||
label: string;
|
||||
}) {
|
||||
const parsed = parseTokenCount(field.value);
|
||||
const display =
|
||||
parsed !== null
|
||||
? formatTokenCountExact(parsed)
|
||||
: approxTotal !== null
|
||||
? `≈ ${formatTokenCountExact(approxTotal)}`
|
||||
displayTotal.kind === "exact"
|
||||
? formatTokenCountExact(displayTotal.value)
|
||||
: displayTotal.kind === "approximate"
|
||||
? `≈ ${formatTokenCountExact(displayTotal.value)}`
|
||||
: null;
|
||||
return (
|
||||
<UsageStat display={display} isPartial={isPartialField(field)} label={label} />
|
||||
<UsageStat display={display} isPartial={displayTotal.partial} label={label} />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,10 @@ import { Tabs, TabsList, TabsTrigger } from "@/shared/ui/tabs";
|
||||
import { useAgentUsageSeries } from "../hooks";
|
||||
import {
|
||||
bigintRatio,
|
||||
deriveApproxTotal,
|
||||
deriveDisplayTotal,
|
||||
formatCoverageDate,
|
||||
formatTokenCountCompact,
|
||||
isPartialField,
|
||||
isUnknownField,
|
||||
parseTokenCount,
|
||||
sortAgentsByKnownTotal,
|
||||
sortAgentsByDisplayTotal,
|
||||
sumKnownBucketTotals,
|
||||
type UsageWindowDays,
|
||||
} from "../lib/agentUsage";
|
||||
@@ -51,7 +48,7 @@ export function AgentUsageSection({
|
||||
const { onOpenSettings } = useAppShell();
|
||||
|
||||
const agents = React.useMemo(
|
||||
() => sortAgentsByKnownTotal(query.data?.agents ?? []),
|
||||
() => sortAgentsByDisplayTotal(query.data?.agents ?? []),
|
||||
[query.data?.agents],
|
||||
);
|
||||
const pubkeys = React.useMemo(
|
||||
@@ -155,17 +152,13 @@ function AgentUsageCard({
|
||||
series.coverage.invalidReportCount > 0;
|
||||
|
||||
// Relative bars are decorative (aria-hidden, per plan) — scale each agent's
|
||||
// known total (or i/o approximation) against the largest such value in the
|
||||
// current window so the sorted-by-total list also reads as a bar chart.
|
||||
const maxKnownTotal = React.useMemo(
|
||||
// display total (exact or approximate) against the largest such value in the
|
||||
// current window so the sorted-by-display-total list also reads as a bar chart.
|
||||
const maxDisplayValue = React.useMemo(
|
||||
() =>
|
||||
agents.reduce<bigint>((max, agent) => {
|
||||
const total = parseTokenCount(agent.usage.totalTokens.value);
|
||||
if (total !== null) return total > max ? total : max;
|
||||
// Fall back to the i/o approximation so bars are still visible when
|
||||
// no agent reports a genuine total.
|
||||
const approx = deriveApproxTotal(agent.usage);
|
||||
return approx !== null && approx > max ? approx : max;
|
||||
const dt = deriveDisplayTotal(agent.usage);
|
||||
return dt.value !== null && dt.value > max ? dt.value : max;
|
||||
}, 0n),
|
||||
[agents],
|
||||
);
|
||||
@@ -229,7 +222,7 @@ function AgentUsageCard({
|
||||
days={days}
|
||||
key={agent.agentPubkey}
|
||||
label={resolveUserLabel({ profiles, pubkey: agent.agentPubkey })}
|
||||
maxKnownTotal={maxKnownTotal}
|
||||
maxDisplayValue={maxDisplayValue}
|
||||
onOpenAgentProfile={onOpenAgentProfile}
|
||||
profileAvatarUrl={
|
||||
profiles?.[agent.agentPubkey]?.avatarUrl ?? null
|
||||
@@ -257,44 +250,28 @@ function AgentUsageRow({
|
||||
agent,
|
||||
days,
|
||||
label,
|
||||
maxKnownTotal,
|
||||
maxDisplayValue,
|
||||
onOpenAgentProfile,
|
||||
profileAvatarUrl,
|
||||
}: {
|
||||
agent: AgentUsage;
|
||||
days: UsageWindowDays;
|
||||
label: string;
|
||||
maxKnownTotal: bigint;
|
||||
maxDisplayValue: bigint;
|
||||
onOpenAgentProfile: (
|
||||
pubkey: string,
|
||||
options?: ProfilePanelOpenOptions,
|
||||
) => void;
|
||||
profileAvatarUrl: string | null;
|
||||
}) {
|
||||
const total = agent.usage.totalTokens;
|
||||
const knownTotal = parseTokenCount(total.value);
|
||||
const partial = isPartialField(total);
|
||||
const unknown = isUnknownField(total);
|
||||
|
||||
// When total is null (unknown), check whether any displayed I/O field is
|
||||
// incomplete — per-field partial truth must be preserved at every seam (A2).
|
||||
const ioPartial =
|
||||
knownTotal === null &&
|
||||
(isPartialField(agent.usage.inputTokens) ||
|
||||
isPartialField(agent.usage.outputTokens));
|
||||
|
||||
// Approximate total for display when no genuine total is available.
|
||||
const approxTotal =
|
||||
knownTotal === null ? deriveApproxTotal(agent.usage) : null;
|
||||
// Effective value for the relative bar width — prefer genuine total, then approx.
|
||||
const barTotal = knownTotal ?? approxTotal;
|
||||
const dt = deriveDisplayTotal(agent.usage);
|
||||
|
||||
const trailing =
|
||||
knownTotal !== null
|
||||
? formatTokenCountCompact(knownTotal)
|
||||
: approxTotal !== null
|
||||
? `≈ ${formatTokenCountCompact(approxTotal)}`
|
||||
: formatIndependentFields(agent);
|
||||
dt.kind === "exact"
|
||||
? formatTokenCountCompact(dt.value)
|
||||
: dt.kind === "approximate"
|
||||
? `≈ ${formatTokenCountCompact(dt.value)}`
|
||||
: "No usage reported";
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -313,34 +290,22 @@ function AgentUsageRow({
|
||||
<span className="block truncate text-sm font-medium text-foreground">
|
||||
{label}
|
||||
</span>
|
||||
{!unknown || approxTotal !== null ? (
|
||||
{dt.kind !== "unknown" ? (
|
||||
<Progress
|
||||
aria-hidden="true"
|
||||
className="mt-1.5 h-1.5"
|
||||
value={
|
||||
barTotal !== null && maxKnownTotal > 0n
|
||||
? bigintRatio(barTotal, maxKnownTotal) * 100
|
||||
maxDisplayValue > 0n
|
||||
? bigintRatio(dt.value, maxDisplayValue) * 100
|
||||
: null
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</span>
|
||||
<span className="flex shrink-0 items-center gap-2 text-sm text-muted-foreground">
|
||||
{partial || ioPartial ? <Badge variant="outline">Partial</Badge> : null}
|
||||
{dt.partial ? <Badge variant="outline">Partial</Badge> : null}
|
||||
{trailing}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function formatIndependentFields(agent: AgentUsage): string {
|
||||
const input = parseTokenCount(agent.usage.inputTokens.value);
|
||||
const output = parseTokenCount(agent.usage.outputTokens.value);
|
||||
if (input !== null || output !== null) {
|
||||
const parts: string[] = [];
|
||||
if (input !== null) parts.push(`in ${formatTokenCountCompact(input)}`);
|
||||
if (output !== null) parts.push(`out ${formatTokenCountCompact(output)}`);
|
||||
return parts.join(" · ");
|
||||
}
|
||||
return "No usage reported";
|
||||
}
|
||||
|
||||
@@ -816,9 +816,13 @@ test("focused view shows daily bars, coverage dates, and a partial explanation w
|
||||
await expect(coverage).toContainText("reported turn");
|
||||
|
||||
// The partial explanation must appear when usage is known-incomplete.
|
||||
// (explainPartial = hasUnknownUsage || invalidReportCount > 0 — both true here.)
|
||||
// The seed has hasUnknownUsage=true AND invalidReportCount=1, so both
|
||||
// per-condition caveat sentences must appear independently.
|
||||
await expect(
|
||||
page.getByTestId("agent-usage-focused-partial-explanation"),
|
||||
page.getByTestId("agent-usage-focused-unknown-intervals-caveat"),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("agent-usage-focused-invalid-reports-caveat"),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user