MCPcopy Index your code
hub / github.com/coder/mux / SessionUsageService

Class SessionUsageService

src/node/services/sessionUsageService.ts:89–526  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

87}
88
89export class SessionUsageService {
90 private readonly SESSION_USAGE_FILE = "session-usage.json";
91 private readonly fileLocks = workspaceFileLocks;
92 private readonly config: Config;
93 private readonly historyService: HistoryService;
94
95 constructor(config: Config, historyService: HistoryService) {
96 this.config = config;
97 this.historyService = historyService;
98 }
99 /**
100 * Collect all messages from iterateFullHistory into an array.
101 * Usage rebuild needs every epoch for accurate totals.
102 */
103 private async collectFullHistory(workspaceId: string): Promise<MuxMessage[]> {
104 const messages: MuxMessage[] = [];
105 const result = await this.historyService.iterateFullHistory(workspaceId, "forward", (chunk) => {
106 messages.push(...chunk);
107 });
108 if (!result.success) {
109 log.warn(`Failed to iterate history for ${workspaceId}: ${result.error}`);
110 return [];
111 }
112 return messages;
113 }
114
115 private getFilePath(workspaceId: string): string {
116 return path.join(this.config.getSessionDir(workspaceId), this.SESSION_USAGE_FILE);
117 }
118
119 private createEmptyUsageFile(): SessionUsageFile {
120 return { byModel: {}, version: 1 };
121 }
122
123 private async readFile(workspaceId: string): Promise<SessionUsageFile> {
124 try {
125 const data = await fs.readFile(this.getFilePath(workspaceId), "utf-8");
126 return JSON.parse(data) as SessionUsageFile;
127 } catch (error) {
128 if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
129 return this.createEmptyUsageFile();
130 }
131 throw error;
132 }
133 }
134
135 private async writeFile(workspaceId: string, data: SessionUsageFile): Promise<void> {
136 const filePath = this.getFilePath(workspaceId);
137 await fs.mkdir(path.dirname(filePath), { recursive: true });
138 await writeFileAtomic(filePath, JSON.stringify(data, null, 2));
139 }
140
141 /**
142 * Record usage from a completed stream. Accumulates with existing usage
143 * AND updates lastRequest in a single atomic write.
144 * Model should already be normalized via normalizeToCanonical().
145 */
146 async recordUsage(workspaceId: string, model: string, usage: ChatUsageDisplay): Promise<void> {

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected