Showing the changes to apps/server/src/usage/usageTranscriptReader.ts (1 of 33 files). Show all
Unified · Split
| @@ -22,6 +22,7 @@ import { · 21 unmodified lines | |||
| 22 | mightCarryUsage, | 22 | mightCarryUsage, |
| 23 | parseClaudeLine, | 23 | parseClaudeLine, |
| 24 | parseCodexLine, | 24 | parseCodexLine, |
| 25 | parseGrokLine, | ||
| 25 | type UsageRecord, | 26 | type UsageRecord, |
| 26 | } from "./usageTranscripts.ts"; | 27 | } from "./usageTranscripts.ts"; |
| 27 | 28 | ||
| @@ -37,12 +38,18 @@ export interface TranscriptFile { · 9 unmodified lines | |||
| 37 | * Errors on individual entries are swallowed: session files rotate and get | 38 | * Errors on individual entries are swallowed: session files rotate and get |
| 38 | * removed while the walk is in flight, and a partial listing is far better than | 39 | * removed while the walk is in flight, and a partial listing is far better than |
| 39 | * failing the page. | 40 | * failing the page. |
| 41 | * | ||
| 42 | * `fileName` restricts the walk to a single basename (Grok's `updates.jsonl`). | ||
| 43 | * Grok sessions also ship multi-megabyte `chat_history` and `events` logs that | ||
| 44 | * never carry usage, so the basename filter keeps a cold scan off those files. | ||
| 40 | */ | 45 | */ |
| 41 | export async function listTranscriptFiles( | 46 | export async function listTranscriptFiles( |
| 42 | root: string, | 47 | root: string, |
| 43 | sinceMs: number, | 48 | sinceMs: number, |
| 49 | options?: { readonly fileName?: string }, | ||
| 44 | ): Promise<readonly TranscriptFile[]> { | 50 | ): Promise<readonly TranscriptFile[]> { |
| 45 | const found: TranscriptFile[] = []; | 51 | const found: TranscriptFile[] = []; |
| 52 | const fileName = options?.fileName; | ||
| 46 | 53 | ||
| 47 | const walk = async (dir: string): Promise<void> => { | 54 | const walk = async (dir: string): Promise<void> => { |
| 48 | let entries; | 55 | let entries; |
| @@ -57,7 +64,11 @@ export async function listTranscriptFiles( · 8 unmodified lines | |||
| 57 | await walk(child); | 64 | await walk(child); |
| 58 | continue; | 65 | continue; |
| 59 | } | 66 | } |
| 60 | if (!entry.name.endsWith(".jsonl")) continue; | 67 | if (fileName !== undefined) { |
| 68 | if (entry.name !== fileName) continue; | ||
| 69 | } else if (!entry.name.endsWith(".jsonl")) { | ||
| 70 | continue; | ||
| 71 | } | ||
| 61 | try { | 72 | try { |
| 62 | const stats = await NodeFSP.stat(child); | 73 | const stats = await NodeFSP.stat(child); |
| 63 | if (stats.mtimeMs >= sinceMs) { | 74 | if (stats.mtimeMs >= sinceMs) { |
| @@ -129,6 +140,12 @@ export async function readTranscriptRecords( · 65 unmodified lines | |||
| 129 | continue; | 140 | continue; |
| 130 | } | 141 | } |
| 131 | 142 | ||
| 143 | if (provider === "grok") { | ||
| 144 | if (!mightCarryUsage(line, provider)) continue; | ||
| 145 | for (const grokRecord of parseGrokLine(line)) records.push(grokRecord); | ||
| 146 | continue; | ||
| 147 | } | ||
| 148 | |||
| 132 | if (!mightCarryUsage(line, provider)) continue; | 149 | if (!mightCarryUsage(line, provider)) continue; |
| 133 | const record = parseClaudeLine(line); | 150 | const record = parseClaudeLine(line); |
| 134 | if (record !== null) records.push(record); | 151 | if (record !== null) records.push(record); |