mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 16:02:42 +00:00
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.
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable max-lines -- Why: OpenCode cost normalization and range, scope, and breakdown policies remain one cohesive store. */
|
||||
import { app } from 'electron'
|
||||
import { join } from 'node:path'
|
||||
import type {
|
||||
@@ -12,49 +11,27 @@ import type {
|
||||
OpenCodeUsageSummary
|
||||
} from '../../shared/opencode-usage-types'
|
||||
import type { Store } from '../persistence'
|
||||
import type { OpenCodeUsageDailyAggregate, OpenCodeUsagePersistedState } from './types'
|
||||
import { OPENCODE_USAGE_SCHEMA_VERSION, openCodeUsageProvider } from './opencode-usage-provider'
|
||||
import { getLocalUsageDay, getUsageRangeCutoff } from '../usage/usage-calendar-range'
|
||||
import type {
|
||||
OpenCodeUsageDailyAggregate,
|
||||
OpenCodeUsagePersistedState,
|
||||
OpenCodeUsageSession
|
||||
} from './types'
|
||||
import { openCodeUsageProvider } from './opencode-usage-provider'
|
||||
import { getDefaultState, normalizePersistedState } from './persisted-state-normalization'
|
||||
import {
|
||||
filterDailyAggregatesByScopeAndRange,
|
||||
filterSessionsByScopeAndRange
|
||||
} from './scope-range-filter'
|
||||
import {
|
||||
buildOpenCodeUsageBreakdownRows,
|
||||
buildOpenCodeUsageDailyPoints,
|
||||
buildOpenCodeUsageRecentSessions,
|
||||
buildOpenCodeUsageSummary
|
||||
} from './snapshot-rollups'
|
||||
import { UsageProviderStoreLifecycle } from '../usage/usage-provider-store-lifecycle'
|
||||
|
||||
const SCHEMA_VERSION = OPENCODE_USAGE_SCHEMA_VERSION
|
||||
|
||||
let _openCodeUsageFile: string | null = null
|
||||
|
||||
function getDefaultState(): OpenCodeUsagePersistedState {
|
||||
return {
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
worktreeFingerprint: null,
|
||||
processedDatabases: [],
|
||||
sessions: [],
|
||||
dailyAggregates: [],
|
||||
scanState: {
|
||||
enabled: false,
|
||||
lastScanStartedAt: null,
|
||||
lastScanCompletedAt: null,
|
||||
lastScanError: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizePersistedState(
|
||||
state: OpenCodeUsagePersistedState
|
||||
): OpenCodeUsagePersistedState {
|
||||
if (state.schemaVersion !== SCHEMA_VERSION) {
|
||||
return getDefaultState()
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
processedDatabases: (state.processedDatabases ?? []).map((database) => ({
|
||||
...database,
|
||||
sessions: (database.sessions ?? []).map(normalizeSessionCost),
|
||||
dailyAggregates: (database.dailyAggregates ?? []).map(normalizeDailyAggregateCost)
|
||||
})),
|
||||
sessions: state.sessions.map(normalizeSessionCost),
|
||||
dailyAggregates: state.dailyAggregates.map(normalizeDailyAggregateCost)
|
||||
}
|
||||
}
|
||||
|
||||
export function initOpenCodeUsagePath(): void {
|
||||
_openCodeUsageFile = join(app.getPath('userData'), 'orca-opencode-usage.json')
|
||||
}
|
||||
@@ -66,43 +43,6 @@ function getOpenCodeUsageFile(): string {
|
||||
return _openCodeUsageFile
|
||||
}
|
||||
|
||||
function addCost(left: number | null, right: number | null): number | null {
|
||||
if (left === null && right === null) {
|
||||
return null
|
||||
}
|
||||
return (left ?? 0) + (right ?? 0)
|
||||
}
|
||||
|
||||
function normalizeDailyAggregateCost(
|
||||
entry: OpenCodeUsageDailyAggregate
|
||||
): OpenCodeUsageDailyAggregate {
|
||||
return {
|
||||
...entry,
|
||||
estimatedCostUsd: entry.estimatedCostUsd ?? null
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeSessionCost(
|
||||
session: OpenCodeUsagePersistedState['sessions'][number]
|
||||
): OpenCodeUsagePersistedState['sessions'][number] {
|
||||
return {
|
||||
...session,
|
||||
estimatedCostUsd: session.estimatedCostUsd ?? null,
|
||||
locationBreakdown: (session.locationBreakdown ?? []).map((entry) => ({
|
||||
...entry,
|
||||
estimatedCostUsd: entry.estimatedCostUsd ?? null
|
||||
})),
|
||||
modelBreakdown: (session.modelBreakdown ?? []).map((entry) => ({
|
||||
...entry,
|
||||
estimatedCostUsd: entry.estimatedCostUsd ?? null
|
||||
})),
|
||||
locationModelBreakdown: (session.locationModelBreakdown ?? []).map((entry) => ({
|
||||
...entry,
|
||||
estimatedCostUsd: entry.estimatedCostUsd ?? null
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenCodeUsageStore extends UsageProviderStoreLifecycle<
|
||||
'processedDatabases',
|
||||
OpenCodeUsagePersistedState,
|
||||
@@ -145,54 +85,12 @@ export class OpenCodeUsageStore extends UsageProviderStoreLifecycle<
|
||||
}
|
||||
|
||||
private buildSummary(scope: OpenCodeUsageScope, range: OpenCodeUsageRange): OpenCodeUsageSummary {
|
||||
const filteredDaily = this.getFilteredDaily(scope, range)
|
||||
const filteredSessions = this.getFilteredSessions(scope, range)
|
||||
|
||||
let inputTokens = 0
|
||||
let cachedInputTokens = 0
|
||||
let outputTokens = 0
|
||||
let reasoningOutputTokens = 0
|
||||
let totalTokens = 0
|
||||
let events = 0
|
||||
let estimatedCostUsd: number | null = null
|
||||
const byModel = new Map<string, number>()
|
||||
const byProject = new Map<string, number>()
|
||||
|
||||
for (const row of filteredDaily) {
|
||||
inputTokens += row.inputTokens
|
||||
cachedInputTokens += row.cachedInputTokens
|
||||
outputTokens += row.outputTokens
|
||||
reasoningOutputTokens += row.reasoningOutputTokens
|
||||
totalTokens += row.totalTokens
|
||||
events += row.eventCount
|
||||
estimatedCostUsd = addCost(estimatedCostUsd, row.estimatedCostUsd)
|
||||
byModel.set(
|
||||
row.model ?? 'Unknown model',
|
||||
(byModel.get(row.model ?? 'Unknown model') ?? 0) + row.totalTokens
|
||||
)
|
||||
byProject.set(row.projectLabel, (byProject.get(row.projectLabel) ?? 0) + row.totalTokens)
|
||||
}
|
||||
|
||||
const topModel =
|
||||
[...byModel.entries()].sort((left, right) => right[1] - left[1])[0]?.[0] ?? null
|
||||
const topProject =
|
||||
[...byProject.entries()].sort((left, right) => right[1] - left[1])[0]?.[0] ?? null
|
||||
|
||||
return {
|
||||
return buildOpenCodeUsageSummary(
|
||||
scope,
|
||||
range,
|
||||
sessions: filteredSessions.length,
|
||||
events,
|
||||
inputTokens,
|
||||
cachedInputTokens,
|
||||
outputTokens,
|
||||
reasoningOutputTokens,
|
||||
totalTokens,
|
||||
estimatedCostUsd,
|
||||
topModel,
|
||||
topProject,
|
||||
hasAnyOpenCodeData: filteredSessions.length > 0 || filteredDaily.length > 0
|
||||
}
|
||||
this.getFilteredDaily(scope, range),
|
||||
this.getFilteredSessions(scope, range)
|
||||
)
|
||||
}
|
||||
|
||||
async getDaily(
|
||||
@@ -207,24 +105,7 @@ export class OpenCodeUsageStore extends UsageProviderStoreLifecycle<
|
||||
scope: OpenCodeUsageScope,
|
||||
range: OpenCodeUsageRange
|
||||
): OpenCodeUsageDailyPoint[] {
|
||||
const byDay = new Map<string, OpenCodeUsageDailyPoint>()
|
||||
for (const row of this.getFilteredDaily(scope, range)) {
|
||||
const existing = byDay.get(row.day) ?? {
|
||||
day: row.day,
|
||||
inputTokens: 0,
|
||||
cachedInputTokens: 0,
|
||||
outputTokens: 0,
|
||||
reasoningOutputTokens: 0,
|
||||
totalTokens: 0
|
||||
}
|
||||
existing.inputTokens += row.inputTokens
|
||||
existing.cachedInputTokens += row.cachedInputTokens
|
||||
existing.outputTokens += row.outputTokens
|
||||
existing.reasoningOutputTokens += row.reasoningOutputTokens
|
||||
existing.totalTokens += row.totalTokens
|
||||
byDay.set(row.day, existing)
|
||||
}
|
||||
return [...byDay.values()].sort((left, right) => left.day.localeCompare(right.day))
|
||||
return buildOpenCodeUsageDailyPoints(this.getFilteredDaily(scope, range))
|
||||
}
|
||||
|
||||
async getBreakdown(
|
||||
@@ -241,56 +122,11 @@ export class OpenCodeUsageStore extends UsageProviderStoreLifecycle<
|
||||
range: OpenCodeUsageRange,
|
||||
kind: OpenCodeUsageBreakdownKind
|
||||
): OpenCodeUsageBreakdownRow[] {
|
||||
const rows = new Map<string, OpenCodeUsageBreakdownRow>()
|
||||
const filteredDaily = this.getFilteredDaily(scope, range)
|
||||
const filteredSessions = this.getFilteredSessions(scope, range)
|
||||
|
||||
for (const daily of filteredDaily) {
|
||||
const key = kind === 'model' ? (daily.model ?? 'unknown') : daily.projectKey
|
||||
const label = kind === 'model' ? (daily.model ?? 'Unknown model') : daily.projectLabel
|
||||
const existing = rows.get(key) ?? {
|
||||
key,
|
||||
label,
|
||||
sessions: 0,
|
||||
events: 0,
|
||||
inputTokens: 0,
|
||||
cachedInputTokens: 0,
|
||||
outputTokens: 0,
|
||||
reasoningOutputTokens: 0,
|
||||
totalTokens: 0,
|
||||
estimatedCostUsd: null
|
||||
}
|
||||
existing.events += daily.eventCount
|
||||
existing.inputTokens += daily.inputTokens
|
||||
existing.cachedInputTokens += daily.cachedInputTokens
|
||||
existing.outputTokens += daily.outputTokens
|
||||
existing.reasoningOutputTokens += daily.reasoningOutputTokens
|
||||
existing.totalTokens += daily.totalTokens
|
||||
existing.estimatedCostUsd = addCost(existing.estimatedCostUsd, daily.estimatedCostUsd)
|
||||
rows.set(key, existing)
|
||||
}
|
||||
|
||||
if (kind === 'model') {
|
||||
for (const session of filteredSessions) {
|
||||
for (const entry of session.modelBreakdown) {
|
||||
const row = rows.get(entry.modelKey)
|
||||
if (row) {
|
||||
row.sessions++
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const session of filteredSessions) {
|
||||
for (const entry of session.locationBreakdown) {
|
||||
const row = rows.get(entry.locationKey)
|
||||
if (row) {
|
||||
row.sessions++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...rows.values()].sort((left, right) => right.totalTokens - left.totalTokens)
|
||||
return buildOpenCodeUsageBreakdownRows(
|
||||
kind,
|
||||
this.getFilteredDaily(scope, range),
|
||||
this.getFilteredSessions(scope, range)
|
||||
)
|
||||
}
|
||||
|
||||
async getRecentSessions(
|
||||
@@ -307,61 +143,20 @@ export class OpenCodeUsageStore extends UsageProviderStoreLifecycle<
|
||||
range: OpenCodeUsageRange,
|
||||
limit = 10
|
||||
): OpenCodeUsageSessionRow[] {
|
||||
return this.getFilteredSessions(scope, range)
|
||||
.slice(0, limit)
|
||||
.map(
|
||||
(session): OpenCodeUsageSessionRow => ({
|
||||
sessionId: session.sessionId,
|
||||
lastActiveAt: session.lastTimestamp,
|
||||
durationMinutes: Math.max(
|
||||
0,
|
||||
Math.round(
|
||||
(new Date(session.lastTimestamp).getTime() -
|
||||
new Date(session.firstTimestamp).getTime()) /
|
||||
60_000
|
||||
)
|
||||
),
|
||||
projectLabel: session.primaryProjectLabel,
|
||||
model: session.primaryModel,
|
||||
events: session.eventCount,
|
||||
inputTokens: session.totalInputTokens,
|
||||
cachedInputTokens: session.totalCachedInputTokens,
|
||||
outputTokens: session.totalOutputTokens,
|
||||
reasoningOutputTokens: session.totalReasoningOutputTokens,
|
||||
totalTokens: session.totalTokens
|
||||
})
|
||||
)
|
||||
return buildOpenCodeUsageRecentSessions(this.getFilteredSessions(scope, range), limit)
|
||||
}
|
||||
|
||||
private getFilteredDaily(
|
||||
scope: OpenCodeUsageScope,
|
||||
range: OpenCodeUsageRange
|
||||
): OpenCodeUsageDailyAggregate[] {
|
||||
const cutoff = getUsageRangeCutoff(range)
|
||||
return this.state.dailyAggregates.filter((row) => {
|
||||
if (scope === 'orca' && !row.worktreeId) {
|
||||
return false
|
||||
}
|
||||
if (cutoff && row.day < cutoff) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return filterDailyAggregatesByScopeAndRange(this.state.dailyAggregates, scope, range)
|
||||
}
|
||||
|
||||
private getFilteredSessions(scope: OpenCodeUsageScope, range: OpenCodeUsageRange) {
|
||||
const cutoff = getUsageRangeCutoff(range)
|
||||
return this.state.sessions.filter((session) => {
|
||||
if (scope === 'orca' && !session.primaryWorktreeId) {
|
||||
return false
|
||||
}
|
||||
if (cutoff) {
|
||||
const day = getLocalUsageDay(session.lastTimestamp)
|
||||
if (!day || day < cutoff) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
private getFilteredSessions(
|
||||
scope: OpenCodeUsageScope,
|
||||
range: OpenCodeUsageRange
|
||||
): OpenCodeUsageSession[] {
|
||||
return filterSessionsByScopeAndRange(this.state.sessions, scope, range)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user