Files
steipete--codexbar/Sources/CodexBarCore/Providers/Grok/GrokLocalSessionScanner.swift
T
2026-07-13 12:22:33 +08:00

101 lines
3.8 KiB
Swift

import Foundation
/// Aggregated stats from local `~/.grok/sessions/**/signals.json` files.
/// Used as a local fallback view when the JSON-RPC billing call is unavailable.
public struct GrokLocalSessionSummary: Sendable {
public let sessionCount: Int
public let totalTokens: Int
public let lastSessionAt: Date?
public let primaryModel: String?
public let models: [String]
public init(
sessionCount: Int,
totalTokens: Int,
lastSessionAt: Date?,
primaryModel: String?,
models: [String])
{
self.sessionCount = sessionCount
self.totalTokens = totalTokens
self.lastSessionAt = lastSessionAt
self.primaryModel = primaryModel
self.models = models
}
}
public enum GrokLocalSessionScanner {
public static let defaultLookbackDays = 30
/// Walk `~/.grok/sessions/<encoded_cwd>/<session_id>/signals.json` and aggregate stats.
public static func summarize(
env: [String: String] = ProcessInfo.processInfo.environment,
fileManager: FileManager = .default,
lookbackDays: Int = defaultLookbackDays,
now: Date = .init()) -> GrokLocalSessionSummary
{
let root = GrokCredentialsStore.grokHomeURL(env: env, fileManager: fileManager)
.appendingPathComponent("sessions", isDirectory: true)
guard let rootEnum = fileManager.enumerator(
at: root,
includingPropertiesForKeys: [.contentModificationDateKey, .isDirectoryKey],
options: [.skipsHiddenFiles])
else {
return GrokLocalSessionSummary(
sessionCount: 0,
totalTokens: 0,
lastSessionAt: nil,
primaryModel: nil,
models: [])
}
let lookbackCutoff = Calendar.current.date(byAdding: .day, value: -lookbackDays, to: now) ?? now
var sessionCount = 0
var totalTokens = 0
var lastSessionAt: Date?
var modelCounts: [String: Int] = [:]
while let url = rootEnum.nextObject() as? URL {
guard url.lastPathComponent == "signals.json" else { continue }
let attrs = try? url.resourceValues(forKeys: [.contentModificationDateKey])
let mtime = attrs?.contentModificationDate ?? Date.distantPast
guard mtime >= lookbackCutoff else { continue }
guard let data = try? Data(contentsOf: url),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
else { continue }
sessionCount += 1
let beforeCompaction = (json["totalTokensBeforeCompaction"] as? Int) ?? 0
let contextUsed = (json["contextTokensUsed"] as? Int) ?? 0
totalTokens += beforeCompaction + contextUsed
if mtime > (lastSessionAt ?? Date.distantPast) {
lastSessionAt = mtime
}
if let primary = (json["primaryModelId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines),
!primary.isEmpty
{
modelCounts[primary, default: 0] += 1
}
if let models = json["modelsUsed"] as? [String] {
for model in models {
let trimmed = model.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmed.isEmpty {
modelCounts[trimmed, default: 0] += 1
}
}
}
}
let sortedModels = modelCounts.sorted { $0.value > $1.value }.map(\.key)
return GrokLocalSessionSummary(
sessionCount: sessionCount,
totalTokens: totalTokens,
lastSessionAt: lastSessionAt,
primaryModel: sortedModels.first,
models: sortedModels)
}
}