fabro/apps/fabro-web/app/lib/usage.ts
Bryan Helmkamp 1cdd78926e
Read Usage in the web app and rename the Billing tab to Usage
The run detail tab, route, query hook, and query key say usage. Every
read of input_tokens, output_tokens, total_tokens, reasoning_tokens,
cache_read_tokens, cache_write_tokens, and total_usd_micros moves to
usage.tokens and usage.cost, with lib/usage.ts replacing lib/billing.ts:
totalTokens sums the five buckets, and costSourceTag names a cost that
the provider reported or that was summed from differently sourced parts.
The Usage tab and the stage popover show that tag next to such a cost.

Test fixtures build a Usage through makeUsage; the failing set of the
web tests is unchanged from main (the same 13 environment failures).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 12:40:04 -06:00

53 lines
1.8 KiB
TypeScript

import type { Cost, TokenCounts, Usage } from "@qltysh/fabro-api-client";
export interface UsageTokenBucket {
label: string;
value: number;
}
/** The sum of the five disjoint token buckets. */
export function totalTokens(usage: Usage): number {
const { input, output, reasoning, cache_read, cache_write } = usage.tokens;
return input + output + reasoning + cache_read + cache_write;
}
/** Output tokens as they are priced: completion plus reasoning. */
export function billableOutputTokens(tokens: TokenCounts): number {
return tokens.output + tokens.reasoning;
}
/** The disjoint token buckets shown in every usage breakdown. */
export function usageTokenBuckets(usage: Usage): UsageTokenBucket[] {
return [
{ label: "Cache read", value: usage.tokens.cache_read },
{ label: "Cache creation", value: usage.tokens.cache_write },
{ label: "Uncached", value: usage.tokens.input },
{ label: "Output", value: billableOutputTokens(usage.tokens) },
];
}
/** Whether the usage carries any tokens or a cost. */
export function hasUsage(usage: Usage): boolean {
return totalTokens(usage) !== 0 || (usage.cost?.usd_micros ?? 0) !== 0;
}
/** The cost in USD micros, when the usage carries one. */
export function costUsdMicros(usage: Usage | null | undefined): number | undefined {
return usage?.cost?.usd_micros;
}
/**
* A short tag for a cost that did not come from the catalog: `reported`
* when the provider gave the figure, `summed` when it was assembled from
* differently sourced parts. Catalog estimates carry no tag.
*/
export function costSourceTag(cost: Cost | null | undefined): string | null {
switch (cost?.source) {
case "provider":
return "reported";
case "application":
return "summed";
default:
return null;
}
}