Skip to content

Commit 6bf7374

Browse files
committed
AGT-22-AGT-61-CLI-Display-Cost-after-each-action
1 parent 81bbfa1 commit 6bf7374

File tree

2 files changed

+132
-21
lines changed

2 files changed

+132
-21
lines changed

src/services/LLM/ModelInfo.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ export class ModelInfo {
1010
private currentModel: string | null = null;
1111
private currentModelInfo: IModelInfo | null = null;
1212
private initialized: boolean = false;
13+
private usageHistory: {
14+
[model: string]: {
15+
prompt_tokens: number;
16+
completion_tokens: number;
17+
total_tokens: number;
18+
}[];
19+
} = {};
1320

1421
constructor(private debugLogger: DebugLogger) {}
1522

@@ -141,4 +148,37 @@ export class ModelInfo {
141148
remaining: contextLength - usedTokens,
142149
});
143150
}
151+
152+
async logDetailedUsage(usage: {
153+
prompt_tokens: number;
154+
completion_tokens: number;
155+
total_tokens: number;
156+
}): Promise<void> {
157+
await this.ensureInitialized();
158+
159+
if (!this.currentModel) {
160+
return;
161+
}
162+
163+
if (!this.usageHistory[this.currentModel]) {
164+
this.usageHistory[this.currentModel] = [];
165+
}
166+
167+
this.usageHistory[this.currentModel].push(usage);
168+
169+
this.debugLogger.log("ModelInfo", "Detailed token usage", {
170+
model: this.currentModel,
171+
usage,
172+
});
173+
}
174+
175+
getUsageHistory(): {
176+
[model: string]: {
177+
prompt_tokens: number;
178+
completion_tokens: number;
179+
total_tokens: number;
180+
}[];
181+
} {
182+
return this.usageHistory;
183+
}
144184
}

src/services/LLMProviders/OpenRouter/OpenRouterAPI.ts

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/* eslint-disable no-useless-catch */
2-
import { ModelScaler } from "@/services/LLM/ModelScaler";
32
import { openRouterClient } from "@constants/openRouterClient";
43
import { ILLMProvider, IMessage } from "@services/LLM/ILLMProvider";
54
import { MessageContextManager } from "@services/LLM/MessageContextManager";
65
import { ModelInfo } from "@services/LLM/ModelInfo";
76
import { ModelManager } from "@services/LLM/ModelManager";
7+
import { ModelScaler } from "@services/LLM/ModelScaler";
88
import {
99
formatMessageContent,
1010
IMessageContent,
@@ -35,6 +35,23 @@ interface IStreamError {
3535
details?: Record<string, unknown>;
3636
}
3737

38+
interface PriceInfo {
39+
prompt: string;
40+
completion: string;
41+
image: string;
42+
request: string;
43+
}
44+
45+
interface UsageEntry {
46+
prompt_tokens: number;
47+
completion_tokens: number;
48+
total_tokens: number;
49+
}
50+
51+
interface UsageHistory {
52+
[modelName: string]: UsageEntry[];
53+
}
54+
3855
@autoInjectable()
3956
export class OpenRouterAPI implements ILLMProvider {
4057
private readonly httpClient: typeof openRouterClient;
@@ -150,6 +167,56 @@ export class OpenRouterAPI implements ILLMProvider {
150167
}));
151168
}
152169

170+
private calculateCosts(priceAll: PriceInfo, usage: UsageHistory) {
171+
const promptRate = parseFloat(priceAll.prompt);
172+
const completionRate = parseFloat(priceAll.completion);
173+
174+
let currentCost = 0;
175+
let totalCost = 0;
176+
177+
for (const modelKey in usage) {
178+
const modelUsage = usage[modelKey];
179+
if (modelUsage.length > 0) {
180+
// Calculate total cost for the model
181+
const modelTotalCost = modelUsage.reduce((sum, entry) => {
182+
const cost =
183+
entry.prompt_tokens * promptRate +
184+
entry.completion_tokens * completionRate;
185+
return sum + cost;
186+
}, 0);
187+
188+
totalCost += modelTotalCost;
189+
190+
// Calculate current cost (last usage entry for the model)
191+
const lastUsage = modelUsage[modelUsage.length - 1];
192+
currentCost =
193+
lastUsage.prompt_tokens * promptRate +
194+
lastUsage.completion_tokens * completionRate;
195+
}
196+
}
197+
198+
return {
199+
currentCost,
200+
totalCost,
201+
};
202+
}
203+
204+
private logChatCosts(
205+
priceAll: PriceInfo | undefined,
206+
usage: UsageHistory,
207+
): void {
208+
if (priceAll && usage) {
209+
const { currentCost, totalCost } = this.calculateCosts(priceAll, usage);
210+
211+
console.log("Current Chat Cost: $", currentCost.toFixed(10));
212+
console.log("Total Chat Cost: $", totalCost.toFixed(10));
213+
} else {
214+
console.log(
215+
"PriceInfo or UsageHistory is undefined, cannot calculate costs.",
216+
);
217+
}
218+
}
219+
153220
async sendMessage(
154221
model: string,
155222
message: string,
@@ -177,9 +244,9 @@ export class OpenRouterAPI implements ILLMProvider {
177244
this.messageContextManager.addMessage("user", message);
178245
this.messageContextManager.addMessage("assistant", assistantMessage);
179246

180-
await this.modelInfo.logCurrentModelUsage(
181-
this.messageContextManager.getTotalTokenCount(),
182-
);
247+
const priceAll = this.modelInfo.getCurrentModelInfo()?.pricing;
248+
const usage = this.modelInfo.getUsageHistory();
249+
this.logChatCosts(priceAll, usage);
183250

184251
return assistantMessage;
185252
} catch (error) {
@@ -296,10 +363,10 @@ export class OpenRouterAPI implements ILLMProvider {
296363
);
297364
}
298365

299-
private processCompleteMessage(message: string): {
366+
private async processCompleteMessage(message: string): Promise<{
300367
content: string;
301368
error?: IStreamError;
302-
} {
369+
}> {
303370
try {
304371
const jsonStr = message.replace(/^data: /, "").trim();
305372
if (
@@ -316,6 +383,10 @@ export class OpenRouterAPI implements ILLMProvider {
316383
return { content: "", error: parsed.error };
317384
}
318385

386+
if (parsed.usage) {
387+
await this.modelInfo.logDetailedUsage(parsed.usage);
388+
}
389+
319390
const deltaContent = parsed.choices?.[0]?.delta?.content;
320391
if (!deltaContent) {
321392
return { content: "" };
@@ -330,10 +401,10 @@ export class OpenRouterAPI implements ILLMProvider {
330401
}
331402
}
332403

333-
private parseStreamChunk(chunk: string): {
404+
private async parseStreamChunk(chunk: string): Promise<{
334405
content: string;
335406
error?: IStreamError;
336-
} {
407+
}> {
337408
this.streamBuffer += chunk;
338409

339410
let content = "";
@@ -343,7 +414,7 @@ export class OpenRouterAPI implements ILLMProvider {
343414
this.streamBuffer = messages.pop() || "";
344415

345416
for (const message of messages) {
346-
const result = this.processCompleteMessage(message);
417+
const result = await this.processCompleteMessage(message);
347418
if (result.error) error = result.error;
348419
content += result.content;
349420
}
@@ -390,8 +461,8 @@ export class OpenRouterAPI implements ILLMProvider {
390461
const stream = response.data;
391462

392463
await new Promise<void>((resolve, reject) => {
393-
stream.on("data", (chunk: Buffer) => {
394-
const { content, error } = this.parseStreamChunk(
464+
stream.on("data", async (chunk: Buffer) => {
465+
const { content, error } = await this.parseStreamChunk(
395466
chunk.toString(),
396467
);
397468

@@ -400,7 +471,7 @@ export class OpenRouterAPI implements ILLMProvider {
400471
const llmError = new LLMError(
401472
error.message || "Stream error",
402473
"STREAM_ERROR",
403-
error.details,
474+
error.details || {},
404475
);
405476
this.handleStreamError(llmError, message, callback);
406477
reject(llmError);
@@ -413,17 +484,17 @@ export class OpenRouterAPI implements ILLMProvider {
413484
}
414485
});
415486

416-
stream.on("end", () => {
487+
stream.on("end", async () => {
417488
if (this.streamBuffer) {
418-
const { content, error } = this.processCompleteMessage(
489+
const { content, error } = await this.parseStreamChunk(
419490
this.streamBuffer,
420491
);
421492
if (error) {
422493
console.log(JSON.stringify(error, null, 2));
423494
const llmError = new LLMError(
424495
error.message || "Stream error",
425496
"STREAM_ERROR",
426-
error.details,
497+
error.details || {},
427498
);
428499
this.handleStreamError(llmError, message, callback);
429500
reject(llmError);
@@ -449,9 +520,9 @@ export class OpenRouterAPI implements ILLMProvider {
449520
assistantMessage,
450521
);
451522

452-
await this.modelInfo.logCurrentModelUsage(
453-
this.messageContextManager.getTotalTokenCount(),
454-
);
523+
const priceAll = this.modelInfo.getCurrentModelInfo()?.pricing;
524+
const usage = this.modelInfo.getUsageHistory();
525+
this.logChatCosts(priceAll, usage);
455526
}
456527
} catch (error) {
457528
throw error;
@@ -470,9 +541,9 @@ export class OpenRouterAPI implements ILLMProvider {
470541
this.messageContextManager.addMessage("user", message);
471542
this.messageContextManager.addMessage("assistant", assistantMessage);
472543

473-
await this.modelInfo.logCurrentModelUsage(
474-
this.messageContextManager.getTotalTokenCount(),
475-
);
544+
const priceAll = this.modelInfo.getCurrentModelInfo()?.pricing;
545+
const usage = this.modelInfo.getUsageHistory();
546+
this.logChatCosts(priceAll, usage);
476547
}
477548
}
478549
}

0 commit comments

Comments
 (0)