mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 18:37:24 +00:00
151 lines
4.8 KiB
TypeScript
151 lines
4.8 KiB
TypeScript
import { codexOpenCodeTokenSessions } from '../usage/agent-token-usage'
|
|
import { app } from 'electron'
|
|
import { join } from 'node:path'
|
|
import type {
|
|
CodexUsageBreakdownKind,
|
|
CodexUsageBreakdownRow,
|
|
CodexUsageDailyPoint,
|
|
CodexUsageRange,
|
|
CodexUsageScope,
|
|
CodexUsageSessionRow,
|
|
CodexUsageSnapshot,
|
|
CodexUsageSummary
|
|
} from '../../shared/codex-usage-types'
|
|
import type { AutomationRunUsage } from '../../shared/automations-types'
|
|
import type { Store } from '../persistence/loading-store/store'
|
|
import type { CodexUsagePersistedState } from './types'
|
|
import type { AutomationUsageLookupInput } from './codex-automation-run-attribution'
|
|
import { CODEX_USAGE_SCHEMA_VERSION, codexUsageProvider } from './codex-usage-provider'
|
|
import { resolveCodexAutomationRunUsage } from './codex-automation-run-attribution'
|
|
import { buildRecentSessions } from './codex-usage-session-rows'
|
|
import { buildBreakdown, buildDaily, buildSummary } from './codex-usage-rollup-projections'
|
|
import { UsageProviderStoreLifecycle } from '../usage/usage-provider-store-lifecycle'
|
|
import { splitUsageCacheFileViaWorker } from '../usage/usage-scan-worker-spawn'
|
|
|
|
const SCHEMA_VERSION = CODEX_USAGE_SCHEMA_VERSION
|
|
|
|
let _codexUsageFile: string | null = null
|
|
|
|
function getDefaultState(): CodexUsagePersistedState {
|
|
return {
|
|
schemaVersion: SCHEMA_VERSION,
|
|
worktreeFingerprint: null,
|
|
processedFiles: [],
|
|
sessions: [],
|
|
dailyAggregates: [],
|
|
scanState: {
|
|
enabled: false,
|
|
lastScanStartedAt: null,
|
|
lastScanCompletedAt: null,
|
|
lastScanError: null
|
|
}
|
|
}
|
|
}
|
|
|
|
export function normalizePersistedState(state: CodexUsagePersistedState): CodexUsagePersistedState {
|
|
if (state.schemaVersion !== SCHEMA_VERSION) {
|
|
// Rebuild incompatible projections while preserving the user's tracking preference.
|
|
const defaults = getDefaultState()
|
|
return {
|
|
...defaults,
|
|
scanState: {
|
|
...defaults.scanState,
|
|
enabled: state.scanState?.enabled ?? defaults.scanState.enabled
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
...state,
|
|
sessions: state.sessions.map((session) => ({
|
|
...session,
|
|
locationModelBreakdown: session.locationModelBreakdown ?? []
|
|
}))
|
|
}
|
|
}
|
|
|
|
export function initCodexUsagePath(): void {
|
|
_codexUsageFile = join(app.getPath('userData'), 'orca-codex-usage.json')
|
|
}
|
|
|
|
function getCodexUsageFile(): string {
|
|
if (!_codexUsageFile) {
|
|
_codexUsageFile = join(app.getPath('userData'), 'orca-codex-usage.json')
|
|
}
|
|
return _codexUsageFile
|
|
}
|
|
|
|
export class CodexUsageStore extends UsageProviderStoreLifecycle<
|
|
'processedFiles',
|
|
CodexUsagePersistedState,
|
|
'hasAnyCodexData'
|
|
> {
|
|
constructor(store: Pick<Store, 'getRepos' | 'getAllWorktreeMeta'>) {
|
|
super(store, {
|
|
tokenUsage: {
|
|
provider: 'codex',
|
|
selectSessions: (state) => codexOpenCodeTokenSessions(state.sessions)
|
|
},
|
|
logTag: '[codex-usage]',
|
|
resolveCacheFile: getCodexUsageFile,
|
|
createDefaultState: getDefaultState,
|
|
normalizeState: normalizePersistedState,
|
|
sourceKey: 'processedFiles',
|
|
dataPresenceKey: 'hasAnyCodexData',
|
|
scan: codexUsageProvider.scan,
|
|
splitCacheFile: splitUsageCacheFileViaWorker
|
|
})
|
|
}
|
|
|
|
getSnapshot(
|
|
scope: CodexUsageScope,
|
|
range: CodexUsageRange,
|
|
recentSessionLimit = 10
|
|
): CodexUsageSnapshot {
|
|
return {
|
|
scanState: this.getScanState(),
|
|
summary: buildSummary(this.state, scope, range),
|
|
daily: buildDaily(this.state, scope, range),
|
|
modelBreakdown: buildBreakdown(this.state, scope, range, 'model'),
|
|
projectBreakdown: buildBreakdown(this.state, scope, range, 'project'),
|
|
recentSessions: buildRecentSessions(this.state, scope, range, recentSessionLimit)
|
|
}
|
|
}
|
|
|
|
async getSummary(scope: CodexUsageScope, range: CodexUsageRange): Promise<CodexUsageSummary> {
|
|
await this.refresh(false)
|
|
return buildSummary(this.state, scope, range)
|
|
}
|
|
|
|
async getDaily(scope: CodexUsageScope, range: CodexUsageRange): Promise<CodexUsageDailyPoint[]> {
|
|
await this.refresh(false)
|
|
return buildDaily(this.state, scope, range)
|
|
}
|
|
|
|
async getBreakdown(
|
|
scope: CodexUsageScope,
|
|
range: CodexUsageRange,
|
|
kind: CodexUsageBreakdownKind
|
|
): Promise<CodexUsageBreakdownRow[]> {
|
|
await this.refresh(false)
|
|
return buildBreakdown(this.state, scope, range, kind)
|
|
}
|
|
|
|
async getRecentSessions(
|
|
scope: CodexUsageScope,
|
|
range: CodexUsageRange,
|
|
limit = 12
|
|
): Promise<CodexUsageSessionRow[]> {
|
|
await this.refresh(false)
|
|
return buildRecentSessions(this.state, scope, range, limit)
|
|
}
|
|
|
|
async getAutomationRunUsage(input: AutomationUsageLookupInput): Promise<AutomationRunUsage> {
|
|
await this.whenLoaded()
|
|
return resolveCodexAutomationRunUsage(input, {
|
|
getState: () => this.state,
|
|
refresh: (force) => this.refresh(force),
|
|
isScanning: () => this.getScanState().isScanning
|
|
})
|
|
}
|
|
}
|