* Persist derived token stats (consumer + file breakdown) as a cache. * * This is intentionally treated as a replaceable cache: if the cache is stale, * the next tokenizer.calculateStats call will overwrite it.
(
workspaceId: string,
cache: SessionUsageTokenStatsCacheV1
)
| 161 | * the next tokenizer.calculateStats call will overwrite it. |
| 162 | */ |
| 163 | async setTokenStatsCache( |
| 164 | workspaceId: string, |
| 165 | cache: SessionUsageTokenStatsCacheV1 |
| 166 | ): Promise<void> { |
| 167 | assert(workspaceId.trim().length > 0, "setTokenStatsCache: workspaceId empty"); |
| 168 | assert(cache.version === 1, "setTokenStatsCache: cache.version must be 1"); |
| 169 | assert(cache.totalTokens >= 0, "setTokenStatsCache: totalTokens must be >= 0"); |
| 170 | assert( |
| 171 | cache.history.messageCount >= 0, |
| 172 | "setTokenStatsCache: history.messageCount must be >= 0" |
| 173 | ); |
| 174 | for (const consumer of cache.consumers) { |
| 175 | assert( |
| 176 | typeof consumer.tokens === "number" && consumer.tokens >= 0, |
| 177 | `setTokenStatsCache: consumer tokens must be >= 0 (${consumer.name})` |
| 178 | ); |
| 179 | } |
| 180 | |
| 181 | return this.fileLocks.withLock(workspaceId, async () => { |
| 182 | // Defensive: don't create new session dirs for already-deleted workspaces. |
| 183 | if (!this.config.findWorkspace(workspaceId)) { |
| 184 | return; |
| 185 | } |
| 186 | |
| 187 | let current: SessionUsageFile; |
| 188 | try { |
| 189 | current = await this.readFile(workspaceId); |
| 190 | } catch { |
| 191 | // Parse errors or other read failures - best-effort rebuild. |
| 192 | log.warn( |
| 193 | `session-usage.json unreadable for ${workspaceId}, rebuilding before token stats cache update` |
| 194 | ); |
| 195 | const messages = await this.collectFullHistory(workspaceId); |
| 196 | if (messages.length > 0) { |
| 197 | await this.rebuildFromMessagesInternal(workspaceId, messages); |
| 198 | current = await this.readFile(workspaceId); |
| 199 | } else { |
| 200 | current = this.createEmptyUsageFile(); |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | current.tokenStatsCache = cache; |
| 205 | await this.writeFile(workspaceId, current); |
| 206 | }); |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * Merge child usage into the parent workspace. |
no test coverage detected