Files
orca/src/main/codex-usage/codex-usage-session-rows.ts
T
Neil 97b71c2285 refactor(usage): split AI-usage scanners and stores under the max-lines budget (#14668)
The three usage scanners and their stores, plus the renderer usage-overview
model, each carried a file-level `eslint-disable max-lines` and had grown to
338-769 counted lines against a 300-line budget. AGENTS.md calls for splitting
rather than suppressing, and config/max-lines-baseline.txt is a shrink-only
ratchet, so this removes all seven suppressions and prunes their entries
(341 -> 334).

Each file is cut along the seams it already had -- and that several of the
suppression comments named out loud: filesystem discovery / record parsing /
attribution / aggregation for the scanners, and pricing policy / scope filters /
rollups / session rows / automation attribution for the stores.

Pure move, no behavior change. Code is relocated verbatim; the only edits are
import plumbing and, where a private class method became a free function, the
mechanical `this.state` -> `state` parameter threading. Every converted call
site passes `this.state` at call time and the automation path takes a live
`getState: () => this.state` getter, so no state is snapshotted. No barrel
exports: each new module owns real logic and importers point at the owner.

Verified: oxlint clean, ratchet passes, typecheck clean, full unit suite green
(remaining failures are pre-existing load flakes in untouched files, each green
when re-run serially), no import cycles among the 64 affected modules, and a
statement-level diff of every split confirms the moves are verbatim.
2026-08-15 18:33:33 -07:00

70 lines
2.4 KiB
TypeScript

import type {
CodexUsageRange,
CodexUsageScope,
CodexUsageSessionRow
} from '../../shared/codex-usage-types'
import type { CodexUsagePersistedState } from './types'
import { getFilteredSessions, getScopedSessionPrimaryModel } from './codex-usage-scope-filters'
export function buildRecentSessions(
state: CodexUsagePersistedState,
scope: CodexUsageScope,
range: CodexUsageRange,
limit = 12
): CodexUsageSessionRow[] {
return getFilteredSessions(state, scope, range)
.slice(0, limit)
.map((session) => {
const matchingLocations = session.locationBreakdown.filter((entry) =>
scope === 'all' ? true : entry.worktreeId !== null
)
const scopedLocations =
matchingLocations.length > 0 ? matchingLocations : session.locationBreakdown
const totals = scopedLocations.reduce(
(acc, entry) => {
acc.events += entry.eventCount
acc.inputTokens += entry.inputTokens
acc.cachedInputTokens += entry.cachedInputTokens
acc.outputTokens += entry.outputTokens
acc.reasoningOutputTokens += entry.reasoningOutputTokens
acc.totalTokens += entry.totalTokens
acc.hasInferredPricing ||= entry.hasInferredPricing
return acc
},
{
events: 0,
inputTokens: 0,
cachedInputTokens: 0,
outputTokens: 0,
reasoningOutputTokens: 0,
totalTokens: 0,
hasInferredPricing: false
}
)
const durationMinutes = Math.max(
0,
Math.round(
(new Date(session.lastTimestamp).getTime() - new Date(session.firstTimestamp).getTime()) /
60_000
)
)
return {
sessionId: session.sessionId,
lastActiveAt: session.lastTimestamp,
durationMinutes,
projectLabel:
scopedLocations.length > 1
? 'Multiple locations'
: (scopedLocations[0]?.projectLabel ?? session.primaryProjectLabel),
model: getScopedSessionPrimaryModel(session, scope),
events: totals.events,
inputTokens: totals.inputTokens,
cachedInputTokens: totals.cachedInputTokens,
outputTokens: totals.outputTokens,
reasoningOutputTokens: totals.reasoningOutputTokens,
totalTokens: totals.totalTokens,
hasInferredPricing: session.hasInferredPricing || totals.hasInferredPricing
}
})
}