Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-dashboard/src/usage.ts
Snapshot at 34e3175
1import type { Brand, ProjectState, Review, Usage, UsageSources } from "./contracts.ts";
2
3export const totalTokens = (usage: Usage) => usage.inputTokens + usage.outputTokens;
4export function tokenNumber(value: number): string {
5 if (value < 100000) return value.toLocaleString("en-US");
6 return `${Number((value / (value >= 1000000 ? 1000000 : 1000)).toFixed(1))}${value >= 1000000 ? "M" : "k"}`;
7}
8export const usageValues = (sources?: UsageSources) =>
9 [sources?.reported, sources?.metered].filter((value): value is Usage => !!value);
10export function usageTooltip(usage: Usage, agent: string, brand: Brand): string {
11 return usage.source === "metered"
12 ? `Metered by AI Gateway for ${agent}.`
13 : `Reported by ${agent} through ${brand.name}. ${brand.name} can't verify it.`;
14}
15/** The current entrant's budget controls promotion; a review's older snapshot never overrides it. */
16export function usagePromotionCaption(review: Review, state: ProjectState): string {
17 const race = state.races?.find((r) => r.packet === review.packet);
18 if (!race || race.tokenBudget == null) return "";
19 const entrant = race.entrants.find(
20 (e) => e.agent === review.agent && e.repoName === review.repoName,
21 );
22 if (!entrant) return "";
23 if (entrant.budget === "over") {
24 const source = entrant.usage?.metered ? "metered" : "reported";
25 return `Over its token budget (${source}). It can't win this race.`;
26 }
27 if (entrant.budget === "unchecked" || !usageValues(entrant.usage).length)
28 return "Report usage first: this race has a token budget.";
29 if (entrant.budget === undefined) return "Budget status unavailable. Refresh before promoting.";
30 return "";
31}
32/** Latest source per binding, not sum of live claims and their review snapshots. */
33export function agentUsageToday(
34 state: ProjectState,
35 agent: string,
36 now = Date.now(),
37): UsageSources {
38 const latest = new Map<string, Usage>();
39 const add = (packet: string, repo: string, sources?: UsageSources) => {
40 for (const value of usageValues(sources)) {
41 const at = Date.parse(value.at);
42 if (!Number.isFinite(at) || at > now) continue;
43 const key = JSON.stringify([packet, repo, value.source]);
44 if (!latest.has(key) || Date.parse(latest.get(key)!.at) < at) latest.set(key, value);
45 }
46 };
47 for (const claim of state.claims.filter((c) => c.active && c.agent === agent))
48 add(claim.packet, claim.repoName, claim.usage);
49 for (const race of state.races ?? [])
50 for (const entrant of race.entrants.filter((e) => e.agent === agent))
51 add(race.packet, entrant.repoName, entrant.usage);
52 for (const review of state.reviews.filter((r) => r.agent === agent))
53 add(review.packet, review.repoName, review.usageAtReview);
54 const start = new Date(now);
55 start.setHours(0, 0, 0, 0);
56 const result: UsageSources = { reported: null, metered: null };
57 for (const value of latest.values()) {
58 if (Date.parse(value.at) < start.getTime()) continue;
59 const sum = result[value.source];
60 result[value.source] = {
61 inputTokens: (sum?.inputTokens ?? 0) + value.inputTokens,
62 outputTokens: (sum?.outputTokens ?? 0) + value.outputTokens,
63 source: value.source,
64 at: !sum || sum.at < value.at ? value.at : sum.at,
65 };
66 }
67 return result;
68}
69/** Graph lanes are binding-specific; historical promotions keep the review snapshot. */
70export function laneUsage(
71 state: ProjectState,
72 lane: { packet: string; repoName: string; review?: Review; promoted?: unknown },
73): UsageSources | undefined {
74 if (lane.promoted) return lane.review?.usageAtReview;
75 return (
76 state.races
77 ?.find((r) => r.packet === lane.packet)
78 ?.entrants.find((e) => e.repoName === lane.repoName)?.usage ??
79 state.claims.find((c) => c.active && c.packet === lane.packet && c.repoName === lane.repoName)
80 ?.usage ??
81 lane.review?.usageAtReview
82 );
83}
84