Skip to content

Commit c991104

Browse files
doctarockedelauna
authored andcommitted
fix: recompute token usage on reads
1 parent 49227da commit c991104

3 files changed

Lines changed: 59 additions & 17 deletions

File tree

src/core/task/Task.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
531531
this.usageTracker = new UsageTracker({
532532
taskId: this.taskId,
533533
getMessages: () => this.clineMessages,
534-
getLastMessageTs: () => this.clineMessages.at(-1)?.ts,
535534
emit: (event, ...args) => (this.emit as any)(event, ...args),
536535
})
537536

@@ -4649,7 +4648,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
46494648
return this.messageQueueService.messages
46504649
}
46514650

4652-
public get tokenUsage(): TokenUsage | undefined {
4651+
public get tokenUsage(): TokenUsage {
46534652
return this.usageTracker.tokenUsage
46544653
}
46554654

src/core/task/UsageTracker.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ type EmitTaskEvent = (event: RooCodeEventName, ...args: any[]) => boolean
1111
export interface UsageTrackerOptions {
1212
taskId: string
1313
getMessages: () => ClineMessage[]
14-
getLastMessageTs: () => number | undefined
1514
emit: EmitTaskEvent
1615
emitIntervalMs?: number
1716
}
@@ -21,25 +20,21 @@ const DEFAULT_TOKEN_USAGE_EMIT_INTERVAL_MS = 2000
2120
export class UsageTracker {
2221
private readonly taskId: string
2322
private readonly getMessages: () => ClineMessage[]
24-
private readonly getLastMessageTs: () => number | undefined
2523
private readonly emit: EmitTaskEvent
2624
private readonly debouncedEmitTokenUsage: ReturnType<typeof debounce>
2725

2826
private tokenUsageSnapshot?: TokenUsage
29-
private tokenUsageSnapshotAt?: number
3027
private toolUsageSnapshot?: ToolUsage
3128
private currentToolUsage: ToolUsage = {}
3229

3330
constructor({
3431
taskId,
3532
getMessages,
36-
getLastMessageTs,
3733
emit,
3834
emitIntervalMs = DEFAULT_TOKEN_USAGE_EMIT_INTERVAL_MS,
3935
}: UsageTrackerOptions) {
4036
this.taskId = taskId
4137
this.getMessages = getMessages
42-
this.getLastMessageTs = getLastMessageTs
4338
this.emit = emit
4439

4540
// Uses debounce with maxWait to achieve throttle-like behavior:
@@ -54,7 +49,6 @@ export class UsageTracker {
5449
if (tokenChanged || toolChanged) {
5550
this.emit(RooCodeEventName.TaskTokenUsageUpdated, this.taskId, tokenUsage, toolUsage)
5651
this.tokenUsageSnapshot = tokenUsage
57-
this.tokenUsageSnapshotAt = this.getLastMessageTs()
5852
this.toolUsageSnapshot = JSON.parse(JSON.stringify(toolUsage))
5953
}
6054
},
@@ -102,15 +96,8 @@ export class UsageTracker {
10296
this.currentToolUsage = toolUsage
10397
}
10498

105-
public get tokenUsage(): TokenUsage | undefined {
106-
if (this.tokenUsageSnapshot && this.tokenUsageSnapshotAt) {
107-
return this.tokenUsageSnapshot
108-
}
109-
110-
this.tokenUsageSnapshot = this.getTokenUsage()
111-
this.tokenUsageSnapshotAt = this.getLastMessageTs()
112-
113-
return this.tokenUsageSnapshot
99+
public get tokenUsage(): TokenUsage {
100+
return this.getTokenUsage()
114101
}
115102

116103
private ensureToolUsageEntry(toolName: ToolName): { attempts: number; failures: number } {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, expect, it, vi } from "vitest"
2+
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
3+
4+
import { UsageTracker } from "../UsageTracker.js"
5+
6+
const createUsageMessage = (text: string, ts = 1000): ClineMessage => ({
7+
type: "say",
8+
say: "api_req_started",
9+
text,
10+
ts,
11+
})
12+
13+
describe("UsageTracker", () => {
14+
it("recomputes tokenUsage on each read after an emitted snapshot exists", () => {
15+
const messages: ClineMessage[] = [
16+
{ type: "say", say: "text", text: "task", ts: 1 },
17+
createUsageMessage('{"tokensIn":100,"tokensOut":50,"cost":0.01}', 1000),
18+
]
19+
const tracker = new UsageTracker({
20+
taskId: "task-id",
21+
getMessages: () => messages,
22+
emit: vi.fn(),
23+
})
24+
25+
tracker.emitTokenUsageUpdate(tracker.getTokenUsage())
26+
expect(tracker.tokenUsage.totalTokensIn).toBe(100)
27+
28+
messages[1] = createUsageMessage('{"tokensIn":250,"tokensOut":50,"cost":0.02}', 1000)
29+
30+
expect(tracker.tokenUsage.totalTokensIn).toBe(250)
31+
expect(tracker.tokenUsage.totalCost).toBe(0.02)
32+
})
33+
34+
it("keeps emitted snapshots for change comparisons", () => {
35+
const emit = vi.fn()
36+
const messages: ClineMessage[] = [
37+
{ type: "say", say: "text", text: "task", ts: 1 },
38+
createUsageMessage('{"tokensIn":100,"tokensOut":50,"cost":0.01}', 1000),
39+
]
40+
const tracker = new UsageTracker({
41+
taskId: "task-id",
42+
getMessages: () => messages,
43+
emit,
44+
})
45+
46+
tracker.emitTokenUsageUpdate(tracker.getTokenUsage())
47+
48+
expect(emit).toHaveBeenCalledWith(
49+
RooCodeEventName.TaskTokenUsageUpdated,
50+
"task-id",
51+
expect.objectContaining({ totalTokensIn: 100 }),
52+
{},
53+
)
54+
expect((tracker as any).tokenUsageSnapshot.totalTokensIn).toBe(100)
55+
})
56+
})

0 commit comments

Comments
 (0)