






















@@ -39,6 +39,7 @@ import type {
3939SessionMessageCounts,
4040SessionModelUsage,
4141SessionUtcQuarterHourMessageCounts,
42+SessionUtcQuarterHourTokenUsage,
4243SessionToolUsage,
4344SessionUsageTimePoint,
4445SessionUsageTimeSeries,
@@ -59,6 +60,7 @@ export type {
5960SessionMessageCounts,
6061SessionModelUsage,
6162SessionUtcQuarterHourMessageCounts,
63+SessionUtcQuarterHourTokenUsage,
6264SessionToolUsage,
6365SessionUsageTimePoint,
6466SessionUsageTimeSeries,
@@ -170,6 +172,14 @@ const formatDayKey = (date: Date): string =>
170172const formatUtcDayKey = (date: Date): string =>
171173`${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
172174175+const getUtcQuarterHourBucketKey = (
176+date: Date,
177+): { date: string; quarterIndex: number; key: string } => {
178+const quarterIndex = Math.floor((date.getUTCHours() * 60 + date.getUTCMinutes()) / 15);
179+const utcDayKey = formatUtcDayKey(date);
180+return { date: utcDayKey, quarterIndex, key: `${utcDayKey}::${quarterIndex}` };
181+};
182+173183/**
174184 * Accumulate message-level counts into a bucket (daily or UTC quarter-hour).
175185 * Avoids duplicating the same logic for both daily and quarter-hour message counts.
@@ -217,15 +227,29 @@ const computeLatencyStats = (values: number[]): SessionLatencyStats | undefined
217227};
218228};
219229230+const computeUsageTokenTotals = (usage: NormalizedUsage) => {
231+const input = usage.input ?? 0;
232+const output = usage.output ?? 0;
233+const cacheRead = usage.cacheRead ?? 0;
234+const cacheWrite = usage.cacheWrite ?? 0;
235+const componentTotal = input + output + cacheRead + cacheWrite;
236+return {
237+ input,
238+ output,
239+ cacheRead,
240+ cacheWrite,
241+ componentTotal,
242+totalTokens: usage.total ?? componentTotal,
243+};
244+};
245+220246const applyUsageTotals = (totals: CostUsageTotals, usage: NormalizedUsage) => {
221-totals.input += usage.input ?? 0;
222-totals.output += usage.output ?? 0;
223-totals.cacheRead += usage.cacheRead ?? 0;
224-totals.cacheWrite += usage.cacheWrite ?? 0;
225-const totalTokens =
226-usage.total ??
227-(usage.input ?? 0) + (usage.output ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
228-totals.totalTokens += totalTokens;
247+const usageTotals = computeUsageTokenTotals(usage);
248+totals.input += usageTotals.input;
249+totals.output += usageTotals.output;
250+totals.cacheRead += usageTotals.cacheRead;
251+totals.cacheWrite += usageTotals.cacheWrite;
252+totals.totalTokens += usageTotals.totalTokens;
229253};
230254231255const applyCostBreakdown = (totals: CostUsageTotals, costBreakdown: CostBreakdown | undefined) => {
@@ -608,6 +632,7 @@ export async function loadSessionCostSummary(params: {
608632const dailyMap = new Map<string, { tokens: number; cost: number }>();
609633const dailyMessageMap = new Map<string, SessionDailyMessageCounts>();
610634const utcQuarterHourMessageMap = new Map<string, SessionUtcQuarterHourMessageCounts>();
635+const utcQuarterHourTokenMap = new Map<string, SessionUtcQuarterHourTokenUsage>();
611636const dailyLatencyMap = new Map<string, number[]>();
612637const dailyModelUsageMap = new Map<string, SessionDailyModelUsage>();
613638const messageCounts: SessionMessageCounts = {
@@ -709,14 +734,10 @@ export async function loadSessionCostSummary(params: {
709734dailyMessageMap.set(dayKey, daily);
710735711736// Per-quarter-hour message counts for precise hourly stats (UTC-based)
712-const quarterIndex = Math.floor(
713-(entry.timestamp.getUTCHours() * 60 + entry.timestamp.getUTCMinutes()) / 15,
714-);
715-const utcDayKey = formatUtcDayKey(entry.timestamp);
716-const quarterKey = `${utcDayKey}::${quarterIndex}`;
717-const utcQuarterHour = utcQuarterHourMessageMap.get(quarterKey) ?? {
718-date: utcDayKey,
719- quarterIndex,
737+const quarterBucket = getUtcQuarterHourBucketKey(entry.timestamp);
738+const utcQuarterHour = utcQuarterHourMessageMap.get(quarterBucket.key) ?? {
739+date: quarterBucket.date,
740+quarterIndex: quarterBucket.quarterIndex,
720741total: 0,
721742user: 0,
722743assistant: 0,
@@ -725,7 +746,7 @@ export async function loadSessionCostSummary(params: {
725746errors: 0,
726747};
727748accumulateMessageCounts(utcQuarterHour, entry, errorStopReasons);
728-utcQuarterHourMessageMap.set(quarterKey, utcQuarterHour);
749+utcQuarterHourMessageMap.set(quarterBucket.key, utcQuarterHour);
729750}
730751731752if (!entry.usage) {
@@ -741,11 +762,11 @@ export async function loadSessionCostSummary(params: {
741762742763if (entry.timestamp) {
743764const dayKey = formatDayKey(entry.timestamp);
744-const entryTokens =
745- (entry.usage.input ?? 0) +
746- (entry.usage.output ?? 0) +
747- (entry.usage.cacheRead ?? 0) +
748- (entry.usage.cacheWrite ?? 0);
765+const entryTokenTotals = computeUsageTokenTotals(entry.usage);
766+// Preserve the legacy dailyBreakdown token basis until daily metrics are
767+// refactored separately. The precise quarter-hour bucket below uses
768+// entryTokenTotals.totalTokens so Usage Mosaic matches session totals.
769+const entryTokens = entryTokenTotals.componentTotal;
749770const entryCost =
750771entry.costBreakdown?.total ??
751772(entry.costBreakdown
@@ -755,6 +776,25 @@ export async function loadSessionCostSummary(params: {
755776(entry.costBreakdown.cacheWrite ?? 0)
756777 : (entry.costTotal ?? 0));
757778779+const quarterBucket = getUtcQuarterHourBucketKey(entry.timestamp);
780+const utcQuarterHourToken = utcQuarterHourTokenMap.get(quarterBucket.key) ?? {
781+date: quarterBucket.date,
782+quarterIndex: quarterBucket.quarterIndex,
783+input: 0,
784+output: 0,
785+cacheRead: 0,
786+cacheWrite: 0,
787+totalTokens: 0,
788+totalCost: 0,
789+};
790+utcQuarterHourToken.input += entryTokenTotals.input;
791+utcQuarterHourToken.output += entryTokenTotals.output;
792+utcQuarterHourToken.cacheRead += entryTokenTotals.cacheRead;
793+utcQuarterHourToken.cacheWrite += entryTokenTotals.cacheWrite;
794+utcQuarterHourToken.totalTokens += entryTokenTotals.totalTokens;
795+utcQuarterHourToken.totalCost += entryCost;
796+utcQuarterHourTokenMap.set(quarterBucket.key, utcQuarterHourToken);
797+758798const existing = dailyMap.get(dayKey) ?? { tokens: 0, cost: 0 };
759799dailyMap.set(dayKey, {
760800tokens: existing.tokens + entryTokens,
@@ -815,6 +855,10 @@ export async function loadSessionCostSummary(params: {
815855utcQuarterHourMessageMap.values(),
816856).toSorted((a, b) => a.date.localeCompare(b.date) || a.quarterIndex - b.quarterIndex);
817857858+const utcQuarterHourTokenUsage: SessionUtcQuarterHourTokenUsage[] = Array.from(
859+utcQuarterHourTokenMap.values(),
860+).toSorted((a, b) => a.date.localeCompare(b.date) || a.quarterIndex - b.quarterIndex);
861+818862const dailyLatency: SessionDailyLatency[] = Array.from(dailyLatencyMap.entries())
819863.map(([date, values]) => {
820864const stats = computeLatencyStats(values);
@@ -865,6 +909,9 @@ export async function loadSessionCostSummary(params: {
865909utcQuarterHourMessageCounts: utcQuarterHourMessageCounts.length
866910 ? utcQuarterHourMessageCounts
867911 : undefined,
912+utcQuarterHourTokenUsage: utcQuarterHourTokenUsage.length
913+ ? utcQuarterHourTokenUsage
914+ : undefined,
868915dailyLatency: dailyLatency.length ? dailyLatency : undefined,
869916dailyModelUsage: dailyModelUsage.length ? dailyModelUsage : undefined,
870917 messageCounts,
@@ -901,11 +948,9 @@ export async function loadSessionUsageTimeSeries(params: {
901948return;
902949}
903950904-const input = entry.usage.input ?? 0;
905-const output = entry.usage.output ?? 0;
906-const cacheRead = entry.usage.cacheRead ?? 0;
907-const cacheWrite = entry.usage.cacheWrite ?? 0;
908-const totalTokens = entry.usage.total ?? input + output + cacheRead + cacheWrite;
951+const { input, output, cacheRead, cacheWrite, totalTokens } = computeUsageTokenTotals(
952+entry.usage,
953+);
909954const cost = entry.costTotal ?? 0;
910955911956cumulativeTokens += totalTokens;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。